-
Notifications
You must be signed in to change notification settings - Fork 2
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
Kevin yoo/hmp 157 add error messages to workspace creation dialog 10 datasets protected datasets selected #3215
Conversation
context/app/static/js/components/workspaces/CreateWorkspaceDialog/CreateWorkspaceDialog.jsx
Outdated
Show resolved
Hide resolved
context/app/static/js/components/workspaces/CreateWorkspaceDialog/CreateWorkspaceDialog.jsx
Outdated
Show resolved
Hide resolved
context/app/static/js/shared-styles/DialogModal/DialogModal.jsx
Outdated
Show resolved
Hide resolved
…s' into kevin-yoo/HMP-157-Add-error-messages-to-workspace-creation-dialog-10-datasets-protected-datasets-selected
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 additional space in between the quotes was creating an empty gap in the dialog.
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.
This was to fix the build error which required the provider wrapping around savedList in order to access the store.
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 SelectableTableProvider
component should be usable in place of the raw Provider
and createStore
calls: https://github.com/hubmapconsortium/portal-ui/blob/main/context/app/static/js/shared-styles/tables/SelectableTableProvider/SelectableTableProvider.jsx#L6
In general though, I'm pretty sure this should be the same store instance that the selected list is saved in; otherwise, you have two instances of the state that may not necessarily be synchronized.
The instance of the zustand store that the saved entities table uses is created as part of the default export: https://github.com/hubmapconsortium/portal-ui/tree/main/context/app/static/js/components/savedLists/SavedEntitiesTable/SavedEntitiesTable.jsx#L130
I think it would make sense to import the version of SavedEntitiesTable
that doesn't have that provider exported with it in this file so that you can guarantee the list and error logic are using the same store instance.
@@ -28,6 +30,11 @@ function DialogModal({ | |||
</Typography> | |||
</StyledDialogTitle> | |||
<DialogContent> | |||
{errorMessage && ( | |||
<Alert $marginBottom={20} severity="error"> |
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.
sx prop for marginBottom did not work so passed in marginBottom prop for Alert
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.
Setting the marginBottom
works fine here, but I'd like to shed some light on why sx
didn't work here and how our upcoming changes will make it work in more places.
The imported Alert
component is a styled-component
with an explicit $marginBottom
prop. The sx
prop's style is thus overwritten by the styles defined for that component (if marginBottom is not in the props, then the margin-bottom: 0px
style is still applied as it's currently implemented).
Once we convert these sorts of components to be handled via MUI's styled
util or via theme, we'll be able to use the sx
prop in more places.
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.
That makes sense.
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.
Great progress! A few minor notes on my end.
@@ -28,6 +30,11 @@ function DialogModal({ | |||
</Typography> | |||
</StyledDialogTitle> | |||
<DialogContent> | |||
{errorMessage && ( | |||
<Alert $marginBottom={20} severity="error"> |
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.
Setting the marginBottom
works fine here, but I'd like to shed some light on why sx
didn't work here and how our upcoming changes will make it work in more places.
The imported Alert
component is a styled-component
with an explicit $marginBottom
prop. The sx
prop's style is thus overwritten by the styles defined for that component (if marginBottom is not in the props, then the margin-bottom: 0px
style is still applied as it's currently implemented).
Once we convert these sorts of components to be handled via MUI's styled
util or via theme, we'll be able to use the sx
prop in more places.
selectedRows.size > 10 | ||
? `You have selected ${selectedRows.size} datasets. Workspaces currently only supports up to 10 datasets. Please unselect datasets.` | ||
: null |
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.
Great job on this change - this should make it a lot easier for us to provide future error messages/feedback in this style.
On a somewhat more optional note: consider defining errorMessage
(and other conditional props in the future) as a variable on the line right after you get selectedRows
:
const { selectedRows } = useStore();
const errorMessage = selectedRows.size > 10 ? 'you have selected ...' : null;
errorMessage={errorMessage}
This approach helps with readability when ternary statements are longer than a single line.
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 SelectableTableProvider
component should be usable in place of the raw Provider
and createStore
calls: https://github.com/hubmapconsortium/portal-ui/blob/main/context/app/static/js/shared-styles/tables/SelectableTableProvider/SelectableTableProvider.jsx#L6
In general though, I'm pretty sure this should be the same store instance that the selected list is saved in; otherwise, you have two instances of the state that may not necessarily be synchronized.
The instance of the zustand store that the saved entities table uses is created as part of the default export: https://github.com/hubmapconsortium/portal-ui/tree/main/context/app/static/js/components/savedLists/SavedEntitiesTable/SavedEntitiesTable.jsx#L130
I think it would make sense to import the version of SavedEntitiesTable
that doesn't have that provider exported with it in this file so that you can guarantee the list and error logic are using the same store instance.
…space-creation-dialog-10-datasets-protected-datasets-selected
Updated demosCreate Workspace dialog with both errors visible: Dismissing the errors reactivates the workspace name field and the launch button: 2023-08-18.workspace.error.workflow.demo.movStorybook demo for the four snackbar variants, which can be triggered from anywhere on the site: 2023-08-18.snackbar.story.demo.movSelecting a single protected dataset properly handles the non-plural case in the error message: |
TODO: Switch over vitessce visualization toasts from sharing config and from viewing vitessce in firefox |
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.
Thanks for picking this up! The only blocking comment is the one made in CreateWorkspaceDialog
- everything else you can take or leave.
context/.storybook/main.js
Outdated
'../app/static/js/shared-styles/**/*.stories.js', | ||
'../app/static/js/shared-styles/**/*.stories.tsx', | ||
'../app/static/js/components/**/*.stories.js', | ||
'../app/static/js/components/**/*.stories.tsx', |
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.
'../app/static/js/shared-styles/**/*.stories.js', | |
'../app/static/js/shared-styles/**/*.stories.tsx', | |
'../app/static/js/components/**/*.stories.js', | |
'../app/static/js/components/**/*.stories.tsx', | |
'../app/static/js/**/*.stories.@(js|jsx|ts|tsx)' |
Perhaps this could simplify it?
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.
Good call!
datasets: (rowCount) => | ||
`You have selected ${rowCount} datasets. Workspaces currently only supports up to 10 datasets. Please unselect datasets.`, | ||
protectedDataset: (rows) => | ||
`You have selected a protected dataset (${rows[0]._source.hubmap_id}). Workspaces currently only supports published public datasets. To remove the protected dataset from workspace creation, click the “Remove Protected Datasets” button below or return to the previous screen to manually remove this dataset.`, | ||
protectedDatasets: (rows) => | ||
`You have selected ${rows.length} protected datasets. Workspaces currently only supports published public datasets. To remove protected datasets from this workspace creation, click the “Remove Protected Datasets” button below or return to the previous screen to manually remove those datasets.`, |
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.
Should we just pass rows for consistency? Both datasets
and protectedDatasets
depend on the length of the rows array.
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 decided to pass the size here initially because selectedRows
is a set, while protectedRows
is an array; I didn't like rows.size
in one case and rows.length
in the other.
I've applied this revision but modified the arg names and added an explanatory comment to ensure clarity re: why this difference is here.
const { selectedRows } = useSelectableTableStore(); | ||
const protectedRows = useDatasetsAccessLevel(selectedRows.size > 0 ? [...selectedRows] : []).datasets; | ||
const containsProtectedDataset = protectedRows.length > 0; | ||
|
||
const errorMessages = []; | ||
|
||
if (selectedRows.size > 10) { | ||
errorMessages.push(errorHelper.datasets(selectedRows.size)); | ||
} | ||
|
||
if (containsProtectedDataset) { | ||
if (protectedRows.length === 1) { | ||
errorMessages.push(errorHelper.protectedDataset(protectedRows)); | ||
} else { | ||
errorMessages.push(errorHelper.protectedDatasets(protectedRows)); | ||
} | ||
} |
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 could add a wrapper component around CreateWorkspaceDialog
and push all of that logic there.
@@ -220,6 +220,8 @@ function createSearchkitFacet({ field, identifier, label, type, ...rest }) { | |||
}); | |||
} | |||
|
|||
const defaultFields = createField({ fieldName: 'mapped_data_access_level', label: 'mapped_data_access_level' }); |
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.
Do we still need this? I remember Kevin and I introduced this at some point, but later moved towards making a request with useDatasetAccessLevel
.
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.
Thanks for the context - I initially left this intact since I wasn't familiar with what this was doing, and have removed it after testing that it doesn't impact functionality.
|
||
const protectedHubmapIds = protectedRows?.map((row) => row._source.hubmap_id).join(', '); | ||
const { toastSuccess } = useSnackbarStore(); | ||
const { deselectRows } = useSelectableTableStore(); | ||
|
||
const removeProtectedDatasets = () => { | ||
deselectRows(protectedRows.map((r) => r._id)); | ||
toastSuccess('Protected datasets successfully removed from selection.'); | ||
}; | ||
|
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.
CreateWorkspaceDialog
is used outside of the search page on both the workspaces page and datasets pages. I don't believe this component will have access to the SelectableTableProvider
store. We should move this to a component around CreateWorkspaceDialog
for use on the search 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.
I've addressed this and the MetadataMenu comment accordingly; I've created a CreateWorkspaceWithDatasetsDialog
component which wraps the CreateWorkspaceDialog
component and performs error checking. The field is then passed in to the CreateWorkspaceDialog
via a renderProp which will also allow us to add any other additional fields we may require in the future.
Following the discussion during yesterday's planning meeting, I have converted the Vitessce snackbars to use the new toast mechanism for consistency. I was not able to trigger the I've also set the Vitessce Firefox performance warning to only pop up once for each user via a localStorage key. This functionality was particularly annoying on publication pages where it would pop up every time the expanded vignette changed. 2023-08-22.vitessce.snackbar.updates.movI'll start work on addressing John's feedback. |
const useSnackbarActions = () => { | ||
return useStore((store) => ({ | ||
toastError: store.toastError, | ||
toastInfo: store.toastInfo, | ||
toastWarning: store.toastWarning, | ||
toastSuccess: store.toastSuccess, | ||
closeSnackbar: store.closeSnackbar, | ||
})); | ||
}; |
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 lean towards not selecting the entire state by default as each component will update when any of the variables change even if the component does not depend on them. We could implement something like https://docs.pmnd.rs/zustand/guides/auto-generating-selectors if writing selectors ourselves is too much boilerplate.
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.
Thanks for the heads up! I agree completely about not selecting the entire state (since that would be more or less identical to using context). The auto generating selectors functionality seems very useful, and would be a solid improvement for us to make once we're on zustand v4.
TL;DR: - I can take this helper out and replace it with specific selectors where needed, but I'm not sure how much benefit that would bring in this case since these are just functions.
To my understanding, since the keys accessed/returned by this helper are functions, they shouldn't be updating at all; the idea was that this helper would let us skip making a separate selector for each case where we just need to be able to send a snackbar message without necessarily needing to read it. Additionally, if zustand functions are regenerated on each state update, wouldn't they all be regenerated regardless of whether they're accessed separately or all together in a helper like this?
// Show a warning if the user is using Firefox. | ||
useEffect(() => { | ||
if (sniffBrowser() === 'Firefox' && !localStorage.getItem(localStorageFirefoxWarningKey)) { | ||
toastError(FIREFOX_WARNING); | ||
localStorage.setItem(localStorageFirefoxWarningKey, true); | ||
} | ||
}, [toastError]); | ||
|
||
// Show instructions for exiting full screen mode when the user enters full screen mode. | ||
useEffect(() => { | ||
if (vizIsFullscreen) { | ||
toastInfo('Press [esc] to exit full window.'); | ||
} | ||
}, [vizIsFullscreen, toastInfo]); |
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 think I prefer to pull related hooks together into a single hook, but it's entirely a preference. Do you prefer to have everything in a single file?
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's on a case-by-case basis for me, but I lean towards keeping hooks focused on handling a single functionality/behavior:
- If there's an interdependent process where separating it leads to the output of one function being fed into another function, I do like to unify those into one on at least some level (e.g. if the SWR output from one fetch is used to set the params of a second fetch, it's helpful to have a single hook which abstracts that detail out from the component, but still keep the other two SWR calls as their own hooks)
- If it's two different conditions that trigger similar effects, I still keep them separated in order to keep the code maintainable and prevent unnecessary computations
- RE:
maintainable
- as time goes on, combining more and more business logic into one hook/into one function leads to big fragile monoliths where - RE:
unnecessary computations
- in this case, combining these twouseEffect
s into one would lead to extrasniffBrowser
executions/potential extralocalStorage
reads whenever the visualization is full-screened; these are trivial operations, but in other circumstances these could be e.g. lookups from an API.
- RE:
…space-creation-dialog-10-datasets-protected-datasets-selected
Note: the fixes in this branch coincide with the changes in #3258 as I initially didn't realize the lint issues came from upstream. If this branch is merged in, we can close that PR. |
…space-creation-dialog-10-datasets-protected-datasets-selected
…space-creation-dialog-10-datasets-protected-datasets-selected
This branch is the main branch for this feature. I have chained other branches from this one to prevent PRs from getting too large.