-
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
[Cases] Improve bulk actions #142150
[Cases] Improve bulk actions #142150
Conversation
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 is looking great! I left a few comments and questions.
One other thing I noticed while testing is that we used to disable the row actions (was only delete) when a row was selected but we're not doing that anymore. I think that's fine just wanted to make sure that was intended.
Old way:
selectedStatus: theCase.status, | ||
}); | ||
|
||
const getPanels = useCallback( |
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 this be a useMemo
?
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 refactor it as you suggested in the comment below. For that reason, it needs to be a callback.
}); | ||
|
||
const getPanels = useCallback( | ||
(): EuiContextMenuPanelDescriptor[] => [ |
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.
nit: I don't know if this is more elegant, but it avoids the multiple checks on canUpdate
and canDelete
:
Pseudo code:
const actionItems = [];
const panels = [{id: 0, items: actionItems, ...}];
if (canUpdate) {
actionItems.push()
panels.push({id: 1, title: 'status', ...})
}
const needsSeparator = actionItems.length > 0
if (canDelete) {
if (needsSeparator) {
actionItems.push({ separator... })
}
actionItems.push({ delete... })
}
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 agree with you. It is much better as you suggested.
const getCasesWithChanges = (cases: Case[], status: CaseStatuses): Case[] => | ||
cases.filter((theCase) => theCase.status !== status); | ||
|
||
const disableStatus = (cases: Case[], status: CaseStatuses) => |
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.
Could we change the name to shouldDisableStatus
?
I think we could simplify it since we don't need to create an array of cases:
const shouldDisableStatus = (...) => cases.some((theCase) => theCase.status !== status);
const getDeleteActionTitle = (totalCases: number): string => | ||
totalCases > 1 ? i18n.BULK_ACTION_DELETE_LABEL : i18n.DELETE_ACTION_LABEL; | ||
|
||
export const useDeleteAction = ({ onAction, onActionSuccess, isDisabled }: UseActionProps) => { |
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 you think it makes sense to move the permissions check within this hook (and the `use_status_action)? I know we'd still need it in the callers to do various other checks though. I think it'd encapsulate the logic a bit more though.
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 moved the logic to the hooks as you suggested. To avoid the consumers doing permissions checks, the hooks will return canDelete
and canUpdateStatus
respectively.
return { | ||
name: <EuiTextColor color={color}>{getDeleteActionTitle(selectedCases.length)}</EuiTextColor>, | ||
onClick: () => openModal(selectedCases), | ||
disabled: isDisabled, |
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 this might be out of scope of this PR but I think we could probably get rid of this prop if we removed the bulk actions
button when not rows are selected. Then we'd only hide this action if the user didn't have deletion permissions.
Gmail also displays additional "bulk" options after you've selected at least 1 row.
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 agree with you. We should hide the "bulk" options if the user has not selected any cases. @mdefazio What do you think?
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 am thinking of leaving the prop in case we need it in the future. I think it is nice for consumers to be able to disable the actions if needed. What do you think?
name: statuses[CaseStatuses.open].label, | ||
icon: getStatusIcon(CaseStatuses.open), | ||
onClick: () => handleUpdateCaseStatus(selectedCases, CaseStatuses.open), | ||
disabled: isDisabled || disableStatus(selectedCases, CaseStatuses.open), |
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.
Is it possible to add a hover text explaining why a status is disabled?
{ | ||
name: ( | ||
<FormattedMessage | ||
defaultMessage="Status: {status}" |
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 you think we need the current status listed? I feel like it's a bit redundant 🤷♂️
Maybe we change it to Change Status
. The deletion action is phrased as Delete Case(s)
so maybe it'd be more consistent to keep with the verbs.
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 a suggestion by @mdefazio. I will the decision to him 🙂
onActionSuccess, | ||
}); | ||
|
||
const panels: EuiContextMenuPanelDescriptor[] = [ |
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 memoize this?
This looks similar to the one in use_actions
: https://github.com/elastic/kibana/pull/142150/files#diff-42bdf17228f1685e18338f8651455291062e9007acaebd184986950f2ec82ebdR53
Should we place it in a hook?
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 change it to a useCallback
and refactored it as you suggested here #142150 (comment)
}, | ||
] | ||
: []), | ||
...(!isSelectorView && (permissions.update || permissions.delete) && actions ? [actions] : []), |
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 you think it'd be cleaner to have the use_actions
hook manage whether a renderable component is returned? Since use_actions
has to do permissions checks anyway maybe we could remove the checks here and have the hook return { actions: null }
if it has no actions to display. So this line would look like:
...(!isSelectorView && actions ? [actions] : []),
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! Btw, I refactored the columns
as you suggested here #142150 (comment)
* Granular permission check for each action is performed | ||
* in useBulkActions | ||
*/ | ||
const showBulkActions = permissions.update || permissions.delete; |
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 comment mentions that read
permissions is required but we're not checking for here. Should this be const showBulkActions = permssions.read;
Would the user even be able to get to this component if they don't have read permissions though? Maybe we don't need to hide them?
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 const showBulkActions = permssions.read;
will show the bulk actions if they have read permissions. We only want to show the bulk actions if they can update
or delete
and not if they can read
. If fixed the comment, it was wrong 🙂
Thanks for the amazing feedback @jonathan-buttner! Regarding disabling the row actions, it was not intentional 🙂. Some tables in Kibana disable the actions some do not. The Rules table for example. @mdefazio What do you think? Should we disable the row actions when selecting multiple cases? |
After going through many of our list/table screens, it seems most of the time, the actions are disabled when selecting a row. This is also the pattern shown on the EUI examples. So I would suggest we follow the common pattern here and disable the row actions. As we update our Rules page, I will be sure to note that there as well. One other note that I had was to remove the 'bulk actions' when there is nothing selected. Since this menu is disabled anyway, there's no need to show this button/menu. (Also similar to the eui pattern linked above where the 'delete' button appears only after you've selected a row). I am reaching out to the other designers in Sec and O11y to make sure they are ok with this change, so we can all be consistent. (Is this a shared component that would update all case pages?) @jonathan-buttner Thanks for catching this one. |
8702b89
to
d96956b
Compare
Thank you for the feedback @mdefazio! I removed the "bulk actions" when there is nothing selected in d96956b and disabled the row actions in 98a2032
Correct! The component is shared to all solutions. |
const showActions = permissions.delete && !isSelectorView; | ||
|
||
const columns = useCasesColumns({ | ||
const { columns } = useCasesColumns({ |
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.
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.
Happy for this to be addressed separately as this hasn't been introduced in this PR.
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.
Thank you @peteharverson! Let's do it on another PR.
@peteharverson @mdefazio Done! I disabled the checkboxes for a user with read-only permissions. |
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.
Mostly nit, but another clarifying question about useCallback
@@ -927,6 +911,20 @@ describe('AllCasesListGeneric', () => { | |||
expect(deleteCasesSpy).toHaveBeenCalledWith(['basic-case-id'], expect.anything()); | |||
}); | |||
}); | |||
|
|||
it('should disable row actions when bulk selecting cases', async () => { |
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.
Could we add a test for disabling the row actions when a single checkbox is selected?
expect(res.queryByTestId('all-cases-modal')).toBeFalsy(); | ||
}); | ||
|
||
it('should hide bulk actions and row actions', async () => { |
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.
Is it hiding them by default because nothing was selected? If so, could we update the name to say why they are hidden, maybe something like:
should hide bulk actions and row actions when no rows are selected
Might be nice to have for the tests below too.
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.
Bulk actions and row actions should not be shown in the modal (on selector view). The user should not perform bulk actions when selecting a case from a modal. I changed the title to make it more clear.
const canDelete = deleteAction.canDelete; | ||
const canUpdate = statusAction.canUpdateStatus; | ||
|
||
const getPanels = useCallback((): EuiContextMenuPanelDescriptor[] => { |
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 might be misunderstanding how useCallback
works, but I think use callback returns a memoized function. So on each rerender the memory address of the function still stays the same but not necessarily the value it returns. That's helpful when we pass the function itself to another component. We're passing the value to another component because we're calling getPanels
panels={getPanels()}
I believe it will call getPanels
on each rendering of the component and the array will get recreated each time.
I think if we were passing the function panels={getPanels}
then we'd want a useCallback
. I think we want useMemo
since we ultimately want an array EuiContextMenuPanelDescriptor[]
and not a function that returns an array right?
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.
oh, you are right!! My mistake 🙂! Fixed.
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 work!
💚 Build Succeeded
Metrics [docs]Module Count
Async chunks
History
To update your PR or re-run it, just comment with: cc @cnasikas |
* Use react query for delete cases * Convert delete to react query * Convert update to react query * Convert use_get_cases_status to react query * Convert use_get_cases_metrics to react query * Refresh metrics and statuses * Show loading when updating cases * Create query key builder * Improve refreshing logic * Improve delete messages * Fix types and tests * Improvements * PR feedback * Fix bug * Refactor actions * Add status actions * Change status to panel * Add status column * Improvements * Fix tests & types * Remove comment * Improve e2e tests * Add unit tests * Add permissions * Fix delete e2e * Disable statuses * Fix i18n * PR feedback * Disable actions when cases are selected * Improve modal tests * Disables checkbox on read only * PR feedback
* Use react query for delete cases * Convert delete to react query * Convert update to react query * Convert use_get_cases_status to react query * Convert use_get_cases_metrics to react query * Refresh metrics and statuses * Show loading when updating cases * Create query key builder * Improve refreshing logic * Improve delete messages * Fix types and tests * Improvements * PR feedback * Fix bug * Refactor actions * Add status actions * Change status to panel * Add status column * Improvements * Fix tests & types * Remove comment * Improve e2e tests * Add unit tests * Add permissions * Fix delete e2e * Disable statuses * Fix i18n * PR feedback * Disable actions when cases are selected * Improve modal tests * Disables checkbox on read only * PR feedback
Summary
This PR refactors the current bulk actions and row actions of the cases table. Specifically:
Remarks:
User flow
bulk_actions.mp4
Screenshots
Bulk actions
Row actions
Table
Disabled status
Permissions
Hide bulk actions
Checklist
Delete any items that are not applicable to this PR.
For maintainers