[Security Solution] Add loading overlay during bulk edit preflight requests (#145905)

## Summary (outdated, see discussion in comments below)

Relates to: https://github.com/elastic/kibana/issues/144264

- Adds additional `isDryRun` boolean option to the `setLoadingRules`
state setter provided by `RulesTableContextProvider`. It is optional and
defaults to `false`.
- Set loading state during pre-flight (dry run) bulk edit requests to
`true`, in order for the loading overlay to display over the Rules Table
while the dry run is executed.

## Context

We currently [have a check that prevents the loading
overlay](https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx#L199)
to display during bulk `edit` API requests (also `enable` and `disable`)
, since that is the current expected UX.

However, we want the loading overlay to display during `dryRun`
requests, but not in the subsequent requests, when the `edit`ing is
actually being done. That is why we need to differentiate the two types
of requests, while maintaining backwards compatibility with the logic
already present. Therefore, the proposal of the optional `isDryRun`
boolean option.

## Before


https://user-images.githubusercontent.com/5354282/203136754-53fb657b-f594-4e91-980f-95de7c11579d.mp4

## After


https://user-images.githubusercontent.com/5354282/203136922-7e90f10d-64d8-4907-8aae-16e7d759107b.mp4

### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Juan Pablo Djeredjian 2022-11-29 12:41:52 +01:00 committed by GitHub
parent 7f8b1f6383
commit 033dcceb8b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 36 additions and 26 deletions

View file

@ -76,7 +76,6 @@ import {
editFirstRule,
goToRuleDetails,
selectNumberOfRules,
waitForRulesTableToBeRefreshed,
} from '../../tasks/alerts_detection_rules';
import { createCustomRuleEnabled } from '../../tasks/api_calls/rules';
import { createTimeline } from '../../tasks/api_calls/timelines';
@ -263,7 +262,6 @@ describe('Custom query rules', () => {
});
deleteFirstRule();
waitForRulesTableToBeRefreshed();
cy.get(RULES_TABLE)
.find(RULES_ROW)
@ -290,7 +288,6 @@ describe('Custom query rules', () => {
selectNumberOfRules(numberOfRulesToBeDeleted);
deleteSelectedRules();
waitForRulesTableToBeRefreshed();
cy.get(RULES_TABLE)
.find(RULES_ROW)

View file

@ -74,7 +74,7 @@ export const useBulkActions = ({
const {
state: { isAllSelected, rules, loadingRuleIds, selectedRuleIds },
actions: { clearRulesSelection },
actions: { clearRulesSelection, setIsPreflightInProgress },
} = rulesTableContext;
const getBulkItemsPopoverContent = useCallback(
@ -195,6 +195,8 @@ export const useBulkActions = ({
closePopover();
setIsPreflightInProgress(true);
const dryRunResult = await executeBulkActionsDryRun({
type: BulkActionType.edit,
...(isAllSelected
@ -203,6 +205,8 @@ export const useBulkActions = ({
editPayload: computeDryRunEditPayload(bulkEditActionType),
});
setIsPreflightInProgress(false);
// User has cancelled edit action or there are no custom rules to proceed
const hasActionBeenConfirmed = await showBulkActionConfirmation(
dryRunResult,
@ -464,15 +468,16 @@ export const useBulkActions = ({
executeBulkAction,
filterQuery,
toasts,
showBulkDuplicateConfirmation,
clearRulesSelection,
confirmDeletion,
bulkExport,
showBulkActionConfirmation,
downloadExportedRules,
setIsPreflightInProgress,
executeBulkActionsDryRun,
filterOptions,
completeBulkEditForm,
downloadExportedRules,
showBulkDuplicateConfirmation,
]
);

View file

@ -28,6 +28,7 @@ export const useRulesTableContextMock = {
order: 'desc',
},
isActionInProgress: false,
isPreflightInProgress: false,
isAllSelected: false,
isFetched: true,
isFetching: false,
@ -49,6 +50,7 @@ export const useRulesTableContextMock = {
setLoadingRules: jest.fn(),
setPage: jest.fn(),
setPerPage: jest.fn(),
setIsPreflightInProgress: jest.fn(),
setSelectedRuleIds: jest.fn(),
setSortingOptions: jest.fn(),
clearRulesSelection: jest.fn(),

View file

@ -59,6 +59,10 @@ export interface RulesTableState {
* Is true then there is no cached data and the query is currently fetching.
*/
isLoading: boolean;
/**
* Is true when a preflight request (dry-run) is in progress.
*/
isPreflightInProgress: boolean;
/**
* Is true whenever a background refetch is in-flight, which does not include initial loading
*/
@ -125,6 +129,7 @@ export interface RulesTableActions {
setFilterOptions: (newFilter: Partial<FilterOptions>) => void;
setIsAllSelected: React.Dispatch<React.SetStateAction<boolean>>;
setIsInMemorySorting: (value: boolean) => void;
setIsPreflightInProgress: React.Dispatch<React.SetStateAction<boolean>>;
/**
* enable/disable rules table auto refresh
*
@ -175,7 +180,11 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide
const [sortingOptions, setSortingOptions] = useState<SortingOptions>(initialSortingOptions);
const [isAllSelected, setIsAllSelected] = useState(false);
const [isRefreshOn, setIsRefreshOn] = useState(autoRefreshSettings.on);
const [loadingRules, setLoadingRules] = useState<LoadingRules>({ ids: [], action: null });
const [loadingRules, setLoadingRules] = useState<LoadingRules>({
ids: [],
action: null,
});
const [isPreflightInProgress, setIsPreflightInProgress] = useState(false);
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(DEFAULT_RULES_PER_PAGE);
const [selectedRuleIds, setSelectedRuleIds] = useState<string[]>([]);
@ -194,12 +203,7 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide
[storage]
);
const isActionInProgress = useMemo(() => {
if (loadingRules.ids.length > 0) {
return !['disable', 'enable', 'edit'].includes(loadingRules.action ?? '');
}
return false;
}, [loadingRules.action, loadingRules.ids.length]);
const isActionInProgress = loadingRules.ids.length > 0;
const pagination = useMemo(() => ({ page, perPage }), [page, perPage]);
@ -262,6 +266,7 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide
total: isInMemorySorting ? rules.length : total,
},
filterOptions,
isPreflightInProgress,
isActionInProgress,
isAllSelected,
isFetched,
@ -287,33 +292,34 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide
setPerPage,
setSelectedRuleIds,
setSortingOptions,
setIsPreflightInProgress,
clearRulesSelection,
},
}),
[
dataUpdatedAt,
rulesToDisplay,
page,
perPage,
isInMemorySorting,
rules.length,
total,
filterOptions,
handleFilterOptionsChange,
isPreflightInProgress,
isActionInProgress,
isAllSelected,
isFetched,
isFetching,
isInMemorySorting,
isLoading,
isRefetching,
isRefreshOn,
loadingRules.action,
dataUpdatedAt,
loadingRules.ids,
page,
perPage,
refetch,
rules.length,
rulesToDisplay,
loadingRules.action,
selectedRuleIds,
sortingOptions,
refetch,
handleFilterOptionsChange,
toggleInMemorySorting,
setSelectedRuleIds,
total,
clearRulesSelection,
]
);

View file

@ -72,7 +72,7 @@ export const RulesTables = React.memo<RulesTableProps>(({ selectedTab }) => {
state: {
rules,
filterOptions,
isActionInProgress,
isPreflightInProgress,
isAllSelected,
isFetched,
isLoading,
@ -229,7 +229,7 @@ export const RulesTables = React.memo<RulesTableProps>(({ selectedTab }) => {
: { 'data-test-subj': 'monitoring-table', columns: monitoringColumns };
const shouldShowLinearProgress = isFetched && isRefetching;
const shouldShowLoadingOverlay = (!isFetched && isRefetching) || isActionInProgress;
const shouldShowLoadingOverlay = (!isFetched && isRefetching) || isPreflightInProgress;
return (
<>