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
Manage Experiments from MLflow UI (Create/Rename/Delete Experiments) #2348
Conversation
Thank you @ggliem for adding the delete/rename feature! The component layout LGTM in this PR. We are currently migrating to antd as our new design language and component library, so we are trying to use antd components if possible in new changes. For this PR I'd suggest using antd Modal and Form as shown in this example or RegisterModelButton instead of ReactModal + Formik. It will be even better if you can point the rename modal in the run page to the new antd modal as well. We'll be glad to review your changes :) |
Based on the following input of the created PR: mlflow#2348 (comment) Updated rename modals to use antd modals and forms instead of ReactModal and Formik.
Thank you @Zangr for your feedback. I started migrating the modals to antd. I changed both the "RenameExperimentModal" as well as the "RenameRunModal" to use antd's Modal&Form instead of ReactModal&Formik in the recent commit. I will update the "DeleteExperimentModal" accordingly. While migrating the "Rename... modals" to antd I noticed the following things:
|
@ggliem Thank you for the refactoring! To solve the 3 issues you mentioned, we can leverage Ref and some imperative ops in componentDidUpdate.
Here is a sample modal implementation with solutions to all you asks. |
Nice, thanks for the input! I will go ahead and change it. |
Based on the following input of the created PR: mlflow#2348 (comment) Updated the generic ConfirmModal to use antd modals.
@Zangr I implemented the changes & ended up refactoring the "Rename" modals even further by putting the shared code in a generic component. I also migrated the ConfirmModal to antd. I noticed that when calling the 'Update Experiment' API ( Update: I opened a PR for this bug fix: #2357 |
@Zangr One more question: I noticed that the CI pipeline failed while executing the "Small" Python (Build: |
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.
Manually tested the code change, works pretty well. I left some comments mostly related to naming/usage convention and discussion wrapper component extraction.
mlflow/server/js/src/components/modals/RenameExperimentModal.js
Outdated
Show resolved
Hide resolved
cc @smurching for the question on python test failure |
Thanks @Zangr for the feedback. I replied to some of your comments where I had some follow-up questions/remarks. |
Added functionality to create experiments from the UI. Added "Create Experiment" button based on mockup in PR mlflow#2348. Added modal and form that handles experiment creation. Added basic unit tests.
@ggliem Thank you for making the change! I got an error banner flashing by when creating a new experiment that already exists in the trash. Can we handle and display the error on the error dialog instead? So users can see what went wrong in one place. Thanks! |
@gioa Good catch, thanks for pointing that out! I totally agree, let's try to make it consistent. You suggested to display the error on the error dialog. However, I'd like to clarify the behavior again before implementing the change. Looking at the error handling of the RegisterModelModal I noticed that the error is displayed in an Antd-Message (the one displayed on the top of the screen in your screenshot), without closing the actual modal nor displaying the error dialog. The actual error is displayed in the message (e.g. "Internal Server Error"). In addition to that, the "original" Formik implementation of the RenameRunFormView displayed the occurred error directly in the RenameRunModal, without closing it. It seems like I ended up mixing the former two options. If an error occurs while creating/deleting/renaming an experiment, should we:
I am leaning towards option 2) since the user might have the chance to directly solve the issue (e.g. modifying the name) - what do you think? |
@ggliem Thank you for looking at all these different implementations and suggesting a better solution! I agree the option 2 is better given the user can update the name directly there. I hope the message does not dismiss itself before users finish reading. IMO an even better option is to display the error directly on the create dialog, similar to RenameRunFormView. So users can see the error and update the experiment's name right there. What do you think? CC @Zangr Shall we look at all the different error handlings and put together a guideline for when to use what components? |
@gioa The time the message is shown is configurable, so we could pick an appropriate time such that it doesn't dismiss itself before users finish reading. I am not sure about the option to display it directly on the dialog. I personally like the message option better. I think a general guideline makes sense such that the errors are handled in a consistent way. |
Agree the create/rename and the delete can be handled differently. Create and rename's errors can be corrected by the user input while delete is often due to a system error - the user cannot do much about it but retrying. Just curious why you prefer the message option rather than display it directly on a dialog. Would love to learn what users think. Also for comparison, I mocked the two options as below. |
@gioa Should we agree on handling the deletion errors in such a way that we display this kind of generic error modal? Thanks for providing the side-by-side comparison. Seeing the direct comparison, I like the mockup in which the error is directly displayed on the dialog. However, one of the reasons I initially did not like the idea of showing it directly in the dialog too much is that the returned error could also be rather technical, e.g. an "Internal Server Error" which does not have to be related to the input field. Hence, I don't know whether the input field should be highlighted. This is how it looked like in the RenameRunFormView which I personally did not like too much: Another thing we could do is handling the "name duplication" error via form validators, similar to the validator in the RegisterModelForm. We could check for name duplication (while renaming or creating experiments) without calling the API. However, the UI currently only keeps track of the active experiments. To make the behavior consistent while using the validators we would probably also have to keep track of the deleted experiments. We could do so by modifying the |
@ggliem Yes, to check the experiment on the client side we can do an on-demand list_experiments call with an extra query parameter ?viewType=ALL and leverage the async validator built-in with antd form to do the validation against current name in the form input. |
@gioa Yes, we should. Overall my feeling is that errors from user actions fit more naturally with the pop ups while 4xx/5xx errors from accessing the main resource of the page during initial visiting can have a dedicated 4xx/5xx page for them. |
@Zangr Do you think it makes more sense to call |
@ggliem Thank you for explaining! Your reasons are quite legit and I agree that the internal server error does not relate to the input, displaying it in a message on the top makes more sense in terms of consistency (I remember seeing internal server error on a message). I like your suggestion of using the form validator to check the input and display an input error directly on the modal. For other system errors like internal server error, we can display in a message. I also assume deletion errors are not input errors, so we can display them in a message as well. |
…itly Incorporated feedback from PR. Aligned with naming conventions (callbacks, variables...) Moved follow-up calls from deleteExperimentApi and deleteRunApi up to be explicitly invoked as a follow up in the then blocks. Updated code to use wrapped dispatch functions explicitly.
Refactored GenericInputModal, making the component more generic. 1. A form component can now be passed to the modal as a render prop 2. The entire values object (from the form) is now passed to the submit callback. The caller can thus decide what to do with the values object.
Updated the experiment deletion behavior. Users can now delete the currently selected experiment. As a consequence, they are re-routed to the root which then picks the next active experiment to show.
Added functionality to create experiments from the UI. Added "Create Experiment" button based on mockup in PR mlflow#2348. Added modal and form that handles experiment creation. Added basic unit tests.
Updated the behavior when creating an experiment. After creation, the user will be redirected to the newly created experiment page. As such, they don not need to manually find the new experiment in the list
Added getExperimentByName to MLflowService. https://www.mlflow.org/docs/latest/rest-api.html#get-experiment-by-name
Unified modal error handling based on the discussion in PR mlflow#2348. Updated Rename/Create experiment modals to check whether the experiment name is already in use. Leveraged async-validator to do the validation against the current experiment name in the form input. Added debouncing to the validation trigger to prevent excessive calls. Other errors, e.g. Internal Server Errors, are now displayed in a message instead of a error dialog.
Updated debounced validator. Before, a new instance of the validator function was created per render. Bound debounce outside the render. Updated some components to use explicit exports.
Updated CreateExperimentModal. A user can now specify an artifact location. Creating an experiment now matches the overall create_experiment signature
2f7a7cf
to
0703b71
Compare
Moved follow-up getExperimentApi call to the rename modal layer.
Updated the experiment creation behavior. Before, the sequence of the performed calls after a user created an experiment was not guaranteed. As such, the experiment list was sometimes not refreshed before the rerouting happened which led to an error in the UI.
I could not reproduce the issue locally, however, it was likely caused by the mentioned issue since that did not guarantee the order in which the promises were resolved. That being said, I do not think that the suggested code works since the state of
Could you please clarify what you would expect this reducer to do? I feel like relying on the follow-up LIST_EXPERIMENT_API action to update the state is fine since some things that are not captured by the createExperimentApi call could change in the future (e.g. default artifact location, additional field when creating an experiment...). |
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 reproduce the issue locally, however, it was likely caused by the mentioned issue since that did not guarantee the order in which the promises were resolved. That being said, I do not think that the suggested code works since the state of
createExperimentApi
promise has to be shared through the promise chain. I pushed a change - could you please check whether you still get the error?
I got your point! However, it's a bit awkward to have to pass down using Promise.all. What do you think about the following implementation with async/await? Also, actually we are not using request ids in this component so we can actually remove them for simplicity.
handleCreateExperiment = async (values) => {
// get values of input fields
const experimentName = values[EXP_NAME_FIELD];
const artifactLocation = values[ARTIFACT_LOCATION];
const response = await this.props.createExperimentApi(experimentName, artifactLocation);
await this.props.listExperimentsApi();
const { value: { experiment_id: newExperimentId } } = response;
if (newExperimentId) {
this.props.history.push(Routes.getExperimentPageRoute(newExperimentId));
}
};
To use the above syntax, you may want to update .eslintrc.js with the following:
'space-before-function-paren': [2, {
'anonymous': 'always',
'named': 'never',
'asyncArrow': 'always',
}],
Could you please clarify what you would expect this reducer to do? I feel like relying on the follow-up LIST_EXPERIMENT_API action to update the state is fine since some things that are not captured by the createExperimentApi call could change in the future (e.g. default artifact location, additional field when creating an experiment...).
You are right. Too bad we don't have the complete experiment metadata in the response. We do need the listing for now. No change is needed for the reducer.
I agree, I initially also looked at the async/await pattern but stuck to the Promise.all implementation since async/await was not used elsewhere in the application. It looks much cleaner now, thanks for the feedback. |
Python tests seem to be failing again for the last commits |
The CI fails when installing Related issue: fbprophet release history: |
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.
LGTM Thank you @ggliem, this is a huge UX improvement for experiment CRUD!
Ready to merge after we did the python build fix.
…lflow#2348) * Add delete and update experiment endpoints to service Added delete and update experiment endpoints to existing MlflowService implementation. These operations were already supported by MLflows REST API. For more information, see: https://www.mlflow.org/docs/latest/rest-api.html Added corresponding AJAX calls. * Add edit and delete button to experiment list Added edit and delete button to experiment list view. The buttons do not have any functionality associated with them yet. * Add delete experiment functionality Added functionality to delete experiments. When clicking on the delete button in the experiment list, confirmation modal is opened. * Refactor and generalize modal to rename runs Updated modal to rename runs such that it is more generic. In particular, updated the displayed form. As such, this component can be used for renaming experiments. * Update delete experiment modal Updated delete experiment modal such that the passed experimentId is a number instead of a string. Updated ExperimentListView accordingly. * Add rename experiment functionality Added functionality to rename experiments. When clicking on the edit button in the experiment list, a modal is opened asking for a new experiment name. * Fix rename experiment functionality Fixed a minor issue which caused the experiment name to not be updated. * Refresh experiment list after deletion Updated experiment deletion functionality such that the shown experiment list is refreshed/updated after performing the deletion. To do so, the behavior of experimentById reducer had to be modified. The previous implementation kept deleted experiments in the state until the page was refreshed. This could also be observed when deleting an experiment via the CLI since the deleted experiment would remain in the UI until refreshing. * Fix existing unit tests * Add unit test Added unit tests testing that the delete and rename experiment modals are opened after clicking the respective links in the experiment list. * Update styles of rename experiment modal * Disable delete experiment link for active experiment Updated the experiment list in such a way that the "Delete Experiment" option is disabled for the currently active/selected experiment. * Fix linting issue * Migrate renaming modals to antd Based on the following input of the created PR: mlflow#2348 (comment) Updated rename modals to use antd modals and forms instead of ReactModal and Formik. * Add focus and initialValue to input in rename modals Updated RenameFormView used in rename modals. The input of the modal will be focused and selected after (re)opening the modal. The input of the modal will contain the respective name of the experiment/run as the initial value after re(opening) the modal. * Create generic input modal & refactor rename modals Refactored rename modals. Since the RenameExperimentModal and RenameRunModal had a lot in common, I put most code in a generic modal component. This generic modal component is used by the rename modals. * Migrate ConfirmModal to antd Based on the following input of the created PR: mlflow#2348 (comment) Updated the generic ConfirmModal to use antd modals. * Update "Delete" icon * Fix error handling of generic modal Fixed error handling of generic modal. Before this change, the error modal was not correctly displayed. * Align with naming conventions & use wrapped dispatch functions explicitly Incorporated feedback from PR. Aligned with naming conventions (callbacks, variables...) Moved follow-up calls from deleteExperimentApi and deleteRunApi up to be explicitly invoked as a follow up in the then blocks. Updated code to use wrapped dispatch functions explicitly. * Add basic rendering test for InputFormView * Update GenericInputModal Refactored GenericInputModal, making the component more generic. 1. A form component can now be passed to the modal as a render prop 2. The entire values object (from the form) is now passed to the submit callback. The caller can thus decide what to do with the values object. * Align with naming conventions inside basic rendering test * Rename InputFormView to RenameForm * Allow users to delete the current experiment Updated the experiment deletion behavior. Users can now delete the currently selected experiment. As a consequence, they are re-routed to the root which then picks the next active experiment to show. * Add "Create Experiment" functionality Added functionality to create experiments from the UI. Added "Create Experiment" button based on mockup in PR mlflow#2348. Added modal and form that handles experiment creation. Added basic unit tests. * Route user to new experiment page after creation Updated the behavior when creating an experiment. After creation, the user will be redirected to the newly created experiment page. As such, they don not need to manually find the new experiment in the list * Add getExperimentByName endpoint to service Added getExperimentByName to MLflowService. https://www.mlflow.org/docs/latest/rest-api.html#get-experiment-by-name * Update error handling & validation checks Unified modal error handling based on the discussion in PR mlflow#2348. Updated Rename/Create experiment modals to check whether the experiment name is already in use. Leveraged async-validator to do the validation against the current experiment name in the form input. Added debouncing to the validation trigger to prevent excessive calls. Other errors, e.g. Internal Server Errors, are now displayed in a message instead of a error dialog. * Update debounced validator usage & use explicit exports Updated debounced validator. Before, a new instance of the validator function was created per render. Bound debounce outside the render. Updated some components to use explicit exports. * Add unit test for experimentNameValidator * Add artifact location to CreateExperimentModal Updated CreateExperimentModal. A user can now specify an artifact location. Creating an experiment now matches the overall create_experiment signature * Update named exports & update lodash import * Update updateExperimentApi Moved follow-up getExperimentApi call to the rename modal layer. * Fix NEP error after experiment creation Updated the experiment creation behavior. Before, the sequence of the performed calls after a user created an experiment was not guaranteed. As such, the experiment list was sometimes not refreshed before the rerouting happened which led to an error in the UI. * Update formatting * Update UUID handling in CreateExperimentModal * Update experiment creation to use async/await pattern
What changes are proposed in this pull request?
Manage Experiments from the UI:
Instead of using the CLI, users can now manage experiments via the UI more easily.
These features (among others) have also been requested in #1028.
The following gif demonstrates the change:
How is this patch tested?
ExperimentListView.test.js
and related formsRelease Notes
Is this a user-facing change?
Enable users to create, delete and rename experiments from the UI.
What component(s) does this PR affect?
How should the PR be classified in the release notes? Choose one:
rn/breaking-change
- The PR will be mentioned in the "Breaking Changes" sectionrn/none
- No description will be included. The PR will be mentioned only by the PR number in the "Small Bugfixes and Documentation Updates" sectionrn/feature
- A new user-facing feature worth mentioning in the release notesrn/bug-fix
- A user-facing bug fix worth mentioning in the release notesrn/documentation
- A user-facing documentation change worth mentioning in the release notes