-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[ML] Trained models: Replace download button by extending deploy action #205699
base: main
Are you sure you want to change the base?
[ML] Trained models: Replace download button by extending deploy action #205699
Conversation
…rations && active operations navigation blocker in models list
try { | ||
await this.ensureModelIsDownloaded(modelId); | ||
|
||
this.addActiveOperation({ modelId, type: 'deploying' }); |
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.
how do you queue several deployments of the same model?
in the cloud environment, the depoyment process can take minutes.
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.
It was a mistake. Previously user could start only one deployment at a time, made changes to follow the same pattern in:
b9d6c5f
At the moment you allow 2 start deployment actions, but not more. Any particular reason for that? Also, it is possible to queue deployments with the same ID. same_deployment_id_issue.mov |
When the user deletes a model after starting a download, we should cancel all queued deployment requests: start_after_delte.mov |
); | ||
|
||
// Navigation blocker when there are active operations | ||
useUnsavedChangesPrompt({ |
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.
We should allow the user to navigate to another Kibana page, but prompt on closing the Kibana browser tab.
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.
done in 5acf8dd
this.modelState$.complete(); | ||
this.downloadStatus$.complete(); | ||
this.stopPolling$.complete(); | ||
this.pollingSubscription?.unsubscribe(); |
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.
it seems like you already unsubscribe in this.stopPolling
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.
Done in 9856fe7
) | ||
.subscribe({ | ||
next: (downloadStatus) => { | ||
const currentItems = this.modelState$.getValue().items; |
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 should use withLatestFrom
operator instead of static getValue
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.
Done in 9856fe7
this.abortedDownloads.add(modelId); | ||
} | ||
|
||
public getModelDownloadState$(modelId: string): Observable<ModelDownloadState | undefined> { |
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.
I could not find the usage of this method anywhere
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.
Also keep in mind, it returns a new Observable instance on each call. It will not work with useObservable
hook as expected.
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.
It was a leftover, removed in 173321f
return this.downloadStatus$.getValue()[modelId]; | ||
} | ||
|
||
private async ensureModelIsDownloaded(modelId: string) { |
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.
Mixing concepts makes it difficult to follow the logic. I would suggest sticking to one, either observables or promises. If you prefer the reactive approach, you should create a queue of model deployments. And this queue should wait for model download completion.
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.
Refactor to follow the more reactive pattern done in: 173321f
if (!refresh) return; | ||
|
||
// Register callback for expanded rows updates | ||
const removeCallback = trainedModelsService.onFetch((modelItems) => { |
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.
What is the motivation for registring a callback? Did you consider using an effect that tracks model items?
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.
Done in: 173321f
}, | ||
} = useMlKibana(); | ||
|
||
const trainedModelsService = useTrainedModelsService(); | ||
const isInitialized = trainedModelsService.isInitialized(); |
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.
It is a static value and the React component will never get this update. We should either get rid of is altogether or make it part of state (or observable)
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.
Removed in: 27f8d9f
useEffect(() => { | ||
return () => { | ||
if (trainedModelsService) { | ||
trainedModelsService.destroy(); |
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.
I reckon we should only destroy on leave if there are no deployments queued
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.
Done in 5acf8dd
…ace-download-button-by-extending-deploy-action
…current operations state
Previously only one deployment could be started at a time, fixed in: |
…ore reactive pattern
…nsavedChangesPrompt non blocking spa functionality
} | ||
|
||
export const useUnsavedChangesPrompt = ({ |
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.
Note: This is just a proposal, so it is possible to use useUnsavedChangesPrompt
without blocking SPA navigation. If you don't like the implementation, we can separate the relevant part of the hook into our package
Pinging @elastic/ml-ui (:ml) |
); | ||
}, | ||
onClick: async (item) => { | ||
if (isModelDownloadItem(item) && item.state === MODEL_STATE.NOT_DOWNLOADED) { |
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.
Was it a deliberate decision to start downloading the model as soon as the deployment modal is opened? I wonder if we should wait to begin the download until the user clicks the 'Start' button in the deployment modal, otherwise there is no way to cancel the download.
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.
The behavior is described in: #195593 (comment)
It is possible to cancel the download by hitting 'Delete Model':
Screen.Recording.2025-01-15.at.14.47.59.mov
// Navigation blocker when there are active operations | ||
useUnsavedChangesPrompt({ | ||
hasUnsavedChanges: activeOperations.length > 0, | ||
blockSpaNavigation: false, |
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.
As discussed, can you add toast notification in to inform the user that a model has finished deploying if they have navigated away from the page?
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.
Done in: 832418b
I will consult on the toast text after the PR is approved.
Screen.Recording.2025-01-15.at.15.51.51.mov
@rbrtj @darnautov do we want to keep the current behavior in the 'Add trained models' flyout, where it just does the download part? |
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.
I like the new prop for the unsaved changes prop hook, thanks @rbrtj!
The UX when you try to deploy a model which is downloaded but not available in the current space could be improved, but this also applies to |
text: i18n.translate( | ||
'xpack.ml.trainedModels.modelsList.startSuccessDescription', | ||
{ | ||
defaultMessage: 'Deployment for "{modelId}" has been started successfully.', |
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.
I'd remove been
from this message. Or do you know the deployment ID at this point, in which case something like
Deployment .elser_model_2_ingest has started successfully.
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.
Either
defaultMessage: 'Deployment for "{modelId}" has been started successfully.', | |
defaultMessage: 'Deployment for "{modelId}" has started successfully.', |
or
defaultMessage: 'Deployment for "{modelId}" has been started successfully.', | |
defaultMessage: '"DeploymentID" has started successfully.', | |
The second one would be better if possible, so we wouldn't need to distinguish between model and deployment (Deployment for modelID
) in the message as it makes the toast harder to skim.
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.
Updated with option 2 in 62b3a61
Additionally, the error message has been updated to include the deploymentId instead of the modelId
…tending-deploy-action
|
|
💔 Build Failed
Failed CI StepsHistory
cc @rbrtj |
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.
UI text LGTM!
Summary
State
column renamed toModel State
Screen.Recording.2025-01-14.at.10.23.55.mov