-
Notifications
You must be signed in to change notification settings - Fork 178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sorting on models page by Last Modified is reflecting changes in versions as well #3597
Changes from 7 commits
537f7c2
52a8b05
4583fc8
7277c59
72d36df
3df03c7
82c76d0
0105b6f
db0a896
c01978e
419ab6d
bb185fd
1d7aba9
637fa40
76773f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import * as React from 'react'; | ||
import { CreateModelVersionData, ModelVersion, ModelState } from '~/concepts/modelRegistry/types'; | ||
import { useModelRegistryAPI } from '~/concepts/modelRegistry/context/ModelRegistryContext'; | ||
|
||
export const useModelVersionCreation = (): { | ||
YuliaKrimerman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
createVersionWithTimestampUpdate: ( | ||
registeredModelId: string, | ||
data: CreateModelVersionData, | ||
opts?: Record<string, unknown>, | ||
) => Promise<ModelVersion>; | ||
apiAvailable: boolean; | ||
} => { | ||
const { api, apiAvailable } = useModelRegistryAPI(); | ||
|
||
const createVersionWithTimestampUpdate = React.useCallback( | ||
async ( | ||
registeredModelId: string, | ||
data: CreateModelVersionData, | ||
opts: Record<string, unknown> = {}, | ||
): Promise<ModelVersion> => { | ||
const newVersion = await api.createModelVersionForRegisteredModel( | ||
opts, | ||
registeredModelId, | ||
data, | ||
); | ||
await api.patchRegisteredModel(opts, { state: ModelState.LIVE }, registeredModelId); | ||
|
||
return newVersion; | ||
}, | ||
[api], | ||
); | ||
|
||
return { | ||
createVersionWithTimestampUpdate, | ||
apiAvailable, | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import { ContextResourceData, CustomWatchK8sResult } from '~/types'; | |
import { TemplateKind } from '~/k8sTypes'; | ||
import { DEFAULT_CONTEXT_DATA, DEFAULT_LIST_WATCH_RESULT } from '~/utilities/const'; | ||
import useTemplateDisablement from '~/pages/modelServing/customServingRuntimes/useTemplateDisablement'; | ||
import { ModelVersion, RegisteredModel } from '~/concepts/modelRegistry/types'; | ||
import useModelRegistryAPIState, { ModelRegistryAPIState } from './useModelRegistryAPIState'; | ||
|
||
export type ModelRegistryContextType = { | ||
|
@@ -67,6 +68,11 @@ export const ModelRegistryContextProvider = conditionalArea<ModelRegistryContext | |
|
||
type UseModelRegistryAPI = ModelRegistryAPIState & { | ||
refreshAllAPI: () => void; | ||
patchModelVersion: (modelVersionId: string, updates: Partial<ModelVersion>) => Promise<void>; | ||
patchRegisteredModel: ( | ||
registeredModelId: string, | ||
updates: Partial<RegisteredModel>, | ||
) => Promise<void>; | ||
}; | ||
|
||
export const useModelRegistryAPI = (): UseModelRegistryAPI => { | ||
|
@@ -75,5 +81,9 @@ export const useModelRegistryAPI = (): UseModelRegistryAPI => { | |
return { | ||
refreshAllAPI, | ||
...apiState, | ||
patchModelVersion: (modelVersionId: string, updates: Partial<ModelVersion>) => | ||
apiState.api.patchModelVersion({}, updates, modelVersionId).then(() => undefined), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand the purpose of the changes in this file. Why add these patch functions to the root of the returned object when they're already returned under |
||
patchRegisteredModel: (registeredModelId: string, updates: Partial<RegisteredModel>) => | ||
apiState.api.patchRegisteredModel({}, updates, registeredModelId).then(() => undefined), | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { | ||
ModelRegistryAPIs, | ||
ModelState, | ||
ModelRegistryMetadataType, | ||
} from '~/concepts/modelRegistry/types'; | ||
|
||
type MinimalModelRegistryAPI = Pick<ModelRegistryAPIs, 'patchRegisteredModel'>; | ||
|
||
export const bumpModelVersionTimestamp = async ( | ||
api: ModelRegistryAPIs, | ||
modelVersionId: string, | ||
): Promise<void> => { | ||
if (!modelVersionId) { | ||
throw new Error('Model version ID is required'); | ||
} | ||
|
||
try { | ||
await api.patchModelVersion({}, { state: ModelState.LIVE }, modelVersionId); | ||
} catch (error) { | ||
throw new Error( | ||
`Failed to update model version timestamp: ${ | ||
error instanceof Error ? error.message : String(error) | ||
}`, | ||
); | ||
} | ||
}; | ||
|
||
export const bumpRegisteredModelTimestamp = async ( | ||
api: MinimalModelRegistryAPI, | ||
registeredModelId: string, | ||
): Promise<void> => { | ||
if (!registeredModelId) { | ||
throw new Error('Registered model ID is required'); | ||
} | ||
|
||
try { | ||
const currentTime = new Date().toISOString(); | ||
await api.patchRegisteredModel( | ||
{}, | ||
{ | ||
state: ModelState.LIVE, | ||
customProperties: { | ||
_lastModified: { | ||
metadataType: ModelRegistryMetadataType.STRING, | ||
// eslint-disable-next-line camelcase | ||
string_value: currentTime, | ||
}, | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's up with this customProperties piece? I don't think it should be necessary, and it'll show up in the properties table in the UI I think all we need is the Also... and this part is more annoying... I wonder if it's ok to just pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking more - if the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add a comment here explaining the workaround and linking to https://issues.redhat.com/browse/RHOAIENG-17614 |
||
}, | ||
registeredModelId, | ||
); | ||
} catch (error) { | ||
throw new Error( | ||
`Failed to update registered model timestamp: ${ | ||
error instanceof Error ? error.message : String(error) | ||
}`, | ||
); | ||
} | ||
}; | ||
|
||
export const bumpBothTimestamps = async ( | ||
api: ModelRegistryAPIs, | ||
modelVersionId: string, | ||
registeredModelId: string, | ||
): Promise<void> => { | ||
await Promise.all([ | ||
bumpModelVersionTimestamp(api, modelVersionId), | ||
bumpRegisteredModelTimestamp(api, registeredModelId), | ||
]); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ import { ModelRegistryContext } from '~/concepts/modelRegistry/context/ModelRegi | |
import ModelTimestamp from '~/pages/modelRegistry/screens/components/ModelTimestamp'; | ||
import { uriToObjectStorageFields } from '~/concepts/modelRegistry/utils'; | ||
import InlineTruncatedClipboardCopy from '~/components/InlineTruncatedClipboardCopy'; | ||
import { bumpBothTimestamps } from '~/concepts/modelRegistry/utils/updateTimestamps'; | ||
|
||
type ModelVersionDetailsViewProps = { | ||
modelVersion: ModelVersion; | ||
|
@@ -47,6 +48,11 @@ const ModelVersionDetailsView: React.FC<ModelVersionDetailsViewProps> = ({ | |
</Bullseye> | ||
); | ||
} | ||
const handleVersionUpdate = async (updatePromise: Promise<unknown>): Promise<void> => { | ||
await updatePromise; | ||
YuliaKrimerman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
await bumpBothTimestamps(apiState.api, mv.id, mv.registeredModelId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the version was just updated, you only need to bump the model timestamp, not both here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried- It didn't work - need both of them . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand though, the updatePromise you pass in is already calling |
||
refresh(); | ||
}; | ||
|
||
return ( | ||
<Flex | ||
|
@@ -64,15 +70,7 @@ const ModelVersionDetailsView: React.FC<ModelVersionDetailsViewProps> = ({ | |
contentWhenEmpty="No description" | ||
value={mv.description || ''} | ||
saveEditedValue={(value) => | ||
apiState.api | ||
.patchModelVersion( | ||
{}, | ||
{ | ||
description: value, | ||
}, | ||
mv.id, | ||
) | ||
.then(refresh) | ||
handleVersionUpdate(apiState.api.patchModelVersion({}, { description: value }, mv.id)) | ||
} | ||
/> | ||
<EditableLabelsDescriptionListGroup | ||
|
@@ -82,15 +80,13 @@ const ModelVersionDetailsView: React.FC<ModelVersionDetailsViewProps> = ({ | |
title="Labels" | ||
contentWhenEmpty="No labels" | ||
onLabelsChange={(editedLabels) => | ||
apiState.api | ||
.patchModelVersion( | ||
handleVersionUpdate( | ||
apiState.api.patchModelVersion( | ||
{}, | ||
{ | ||
customProperties: mergeUpdatedLabels(mv.customProperties, editedLabels), | ||
}, | ||
{ customProperties: mergeUpdatedLabels(mv.customProperties, editedLabels) }, | ||
mv.id, | ||
) | ||
.then(refresh) | ||
), | ||
) | ||
} | ||
data-testid="model-version-labels" | ||
/> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
import * as React from 'react'; | ||
import { useCallback } from 'react'; | ||
import { useParams } from 'react-router-dom'; | ||
import { useModelRegistryAPI } from '~/concepts/modelRegistry/context/ModelRegistryContext'; | ||
import { Table } from '~/components/table'; | ||
import { ModelVersion } from '~/concepts/modelRegistry/types'; | ||
import { ModelVersion, ModelState } from '~/concepts/modelRegistry/types'; | ||
import DashboardEmptyTableView from '~/concepts/dashboard/DashboardEmptyTableView'; | ||
import useInferenceServices from '~/pages/modelServing/useInferenceServices'; | ||
import { useMakeFetchObject } from '~/utilities/useMakeFetchObject'; | ||
|
@@ -25,10 +27,31 @@ const ModelVersionsTable: React.FC<ModelVersionsTableProps> = ({ | |
}) => { | ||
const { registeredModelId } = useParams(); | ||
const inferenceServices = useMakeFetchObject(useInferenceServices(undefined, registeredModelId)); | ||
const modelRegistryApi = useModelRegistryAPI(); | ||
|
||
const handleAfterDeploy = useCallback( | ||
async (modelVersionId: string) => { | ||
if (!registeredModelId) { | ||
return; | ||
} | ||
|
||
try { | ||
await Promise.all([ | ||
modelRegistryApi.patchModelVersion(modelVersionId, { state: ModelState.LIVE }), | ||
modelRegistryApi.patchRegisteredModel(registeredModelId, { state: ModelState.LIVE }), | ||
]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you remove the changes you made to ModelRegistryContext, you can just rewrite these calls using the stuff already returned by await Promise.all([
modelRegistryApi.api.patchModelVersion({}, { state: ModelState.LIVE }, modelVersionId),
modelRegistryApi.api.patchRegisteredModel(
{},
{ state: ModelState.LIVE },
registeredModelId,
),
]); I wonder if the reason for these extra functions was because you didn't see them exposed on the root of the returned There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also - as mentioned elsewhere, since you'll need this same behavior for both of the places where we can deploy (the Lastly, you can probably use your new util |
||
} catch (error) { | ||
throw new Error('Failed to update timestamps after deployment'); | ||
} | ||
}, | ||
[registeredModelId, modelRegistryApi], | ||
); | ||
|
||
const hasDeploys = (mvId: string) => | ||
!!inferenceServices.data.some( | ||
(s) => s.metadata.labels?.[KnownLabels.MODEL_VERSION_ID] === mvId, | ||
); | ||
|
||
return ( | ||
<Table | ||
data-testid="model-versions-table" | ||
|
@@ -46,6 +69,7 @@ const ModelVersionsTable: React.FC<ModelVersionsTableProps> = ({ | |
modelVersion={mv} | ||
isArchiveModel={isArchiveModel} | ||
refresh={refresh} | ||
onAfterDeploy={handleAfterDeploy} | ||
/> | ||
)} | ||
/> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ type ModelVersionsTableRowProps = { | |
isArchiveModel?: boolean; | ||
hasDeployment?: boolean; | ||
refresh: () => void; | ||
onAfterDeploy?: (modelVersionId: string) => Promise<void>; | ||
}; | ||
|
||
const ModelVersionsTableRow: React.FC<ModelVersionsTableRowProps> = ({ | ||
|
@@ -31,6 +32,7 @@ const ModelVersionsTableRow: React.FC<ModelVersionsTableRowProps> = ({ | |
isArchiveModel, | ||
hasDeployment = false, | ||
refresh, | ||
onAfterDeploy, | ||
}) => { | ||
const navigate = useNavigate(); | ||
const { preferredModelRegistry } = React.useContext(ModelRegistrySelectorContext); | ||
|
@@ -39,6 +41,12 @@ const ModelVersionsTableRow: React.FC<ModelVersionsTableRowProps> = ({ | |
const [isDeployModalOpen, setIsDeployModalOpen] = React.useState(false); | ||
const { apiState } = React.useContext(ModelRegistryContext); | ||
|
||
const handleAfterDeploy = async () => { | ||
if (onAfterDeploy) { | ||
await onAfterDeploy(mv.id); | ||
} | ||
}; | ||
|
||
const actions: IAction[] = isArchiveRow | ||
? [ | ||
{ | ||
|
@@ -127,15 +135,16 @@ const ModelVersionsTableRow: React.FC<ModelVersionsTableRowProps> = ({ | |
) : null} | ||
{isDeployModalOpen ? ( | ||
<DeployRegisteredModelModal | ||
onSubmit={() => | ||
onSubmit={async () => { | ||
await handleAfterDeploy(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you also need to add this to where DeployRegisteredModelModal gets rendered in ModelVersionDetailsHeaderActions |
||
navigate( | ||
modelVersionDeploymentsUrl( | ||
mv.id, | ||
mv.registeredModelId, | ||
preferredModelRegistry?.metadata.name, | ||
), | ||
) | ||
} | ||
); | ||
}} | ||
onCancel={() => setIsDeployModalOpen(false)} | ||
modelVersion={mv} | ||
/> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,11 +52,10 @@ const DeployRegisteredModelModal: React.FC<DeployRegisteredModelModalProps> = ({ | |
} = useRegisteredModelDeployInfo(modelVersion); | ||
|
||
const onClose = React.useCallback( | ||
(submit: boolean) => { | ||
async (submit: boolean) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I see why this was made async, I can't find anywhere it's awaited and it's not awaiting anything |
||
if (submit) { | ||
onSubmit?.(); | ||
} | ||
|
||
setSelectedProject(null); | ||
onCancel(); | ||
}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might be able to just use the
patchRegisteredModel
exported from this same file below (already in scope inside this function), it returns the same function you're writing here.