[Security Solution][Exceptions] - Fix stale linked rules count on manage rules save (#155108)

## Summary

Addresses https://github.com/elastic/kibana/issues/153195
This commit is contained in:
Yara Tercero 2023-04-20 11:23:15 -07:00 committed by GitHub
parent dd46350cac
commit ada91f9a5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 68 additions and 15 deletions

View file

@ -14,7 +14,7 @@ import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../..
import { EXCEPTIONS_URL } from '../../../urls/navigation'; import { EXCEPTIONS_URL } from '../../../urls/navigation';
import { import {
deleteExceptionListWithRuleReference, deleteExceptionListWithRuleReferenceByListId,
deleteExceptionListWithoutRuleReference, deleteExceptionListWithoutRuleReference,
exportExceptionList, exportExceptionList,
searchForExceptionList, searchForExceptionList,
@ -79,7 +79,7 @@ describe('Exceptions Table', () => {
visitWithoutDateRange(EXCEPTIONS_URL); visitWithoutDateRange(EXCEPTIONS_URL);
waitForExceptionsTableToBeLoaded(); waitForExceptionsTableToBeLoaded();
exportExceptionList(); exportExceptionList(getExceptionList1().list_id);
cy.wait('@export').then(({ response }) => { cy.wait('@export').then(({ response }) => {
cy.wrap(response?.body).should( cy.wrap(response?.body).should(
@ -168,7 +168,7 @@ describe('Exceptions Table', () => {
// just checking number of lists shown // just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2'); cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2');
deleteExceptionListWithRuleReference(); deleteExceptionListWithRuleReferenceByListId(getExceptionList2().list_id);
// Using cy.contains because we do not care about the exact text, // Using cy.contains because we do not care about the exact text,
// just checking number of lists shown // just checking number of lists shown

View file

@ -14,11 +14,13 @@ import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../..
import { EXCEPTIONS_URL } from '../../../urls/navigation'; import { EXCEPTIONS_URL } from '../../../urls/navigation';
import { import {
deleteExceptionListWithRuleReference, deleteExceptionListWithRuleReferenceByListId,
deleteExceptionListWithoutRuleReference, deleteExceptionListWithoutRuleReference,
exportExceptionList, exportExceptionList,
waitForExceptionsTableToBeLoaded, waitForExceptionsTableToBeLoaded,
createSharedExceptionList, createSharedExceptionList,
linkRulesToExceptionList,
assertNumberLinkedRules,
} from '../../../tasks/exceptions_table'; } from '../../../tasks/exceptions_table';
import { import {
EXCEPTIONS_LIST_MANAGEMENT_NAME, EXCEPTIONS_LIST_MANAGEMENT_NAME,
@ -46,6 +48,8 @@ describe('Manage shared exception list', () => {
esArchiverResetKibana(); esArchiverResetKibana();
login(); login();
createRule(getNewRule({ name: 'Another rule' }));
// Create exception list associated with a rule // Create exception list associated with a rule
createExceptionList(getExceptionList2(), getExceptionList2().list_id).then((response) => createExceptionList(getExceptionList2(), getExceptionList2().list_id).then((response) =>
createRule( createRule(
@ -76,7 +80,7 @@ describe('Manage shared exception list', () => {
it('Export exception list', function () { it('Export exception list', function () {
cy.intercept(/(\/api\/exception_lists\/_export)/).as('export'); cy.intercept(/(\/api\/exception_lists\/_export)/).as('export');
exportExceptionList(); exportExceptionList(getExceptionList1().list_id);
cy.wait('@export').then(({ response }) => { cy.wait('@export').then(({ response }) => {
cy.wrap(response?.body).should( cy.wrap(response?.body).should(
@ -91,6 +95,12 @@ describe('Manage shared exception list', () => {
}); });
}); });
it('Link rules to shared exception list', function () {
assertNumberLinkedRules(getExceptionList2().list_id, '1');
linkRulesToExceptionList(getExceptionList2().list_id, 1);
assertNumberLinkedRules(getExceptionList2().list_id, '2');
});
it('Create exception list', function () { it('Create exception list', function () {
createSharedExceptionList({ name: EXCEPTION_LIST_NAME, description: 'This is my list.' }, true); createSharedExceptionList({ name: EXCEPTION_LIST_NAME, description: 'This is my list.' }, true);
@ -118,7 +128,7 @@ describe('Manage shared exception list', () => {
// just checking number of lists shown // just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '3'); cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '3');
deleteExceptionListWithRuleReference(); deleteExceptionListWithRuleReferenceByListId(getExceptionList2().list_id);
// Using cy.contains because we do not care about the exact text, // Using cy.contains because we do not care about the exact text,
// just checking number of lists shown // just checking number of lists shown

View file

@ -49,6 +49,9 @@ export const EXCEPTIONS_TABLE_SHOWING_LISTS = '[data-test-subj="showingException
export const EXCEPTIONS_TABLE_DELETE_BTN = export const EXCEPTIONS_TABLE_DELETE_BTN =
'[data-test-subj="sharedListOverflowCardActionItemDelete"]'; '[data-test-subj="sharedListOverflowCardActionItemDelete"]';
export const EXCEPTIONS_TABLE_LINK_RULES_BTN =
'[data-test-subj="sharedListOverflowCardActionItemLinkRules"]';
export const EXCEPTIONS_TABLE_EXPORT_MODAL_BTN = export const EXCEPTIONS_TABLE_EXPORT_MODAL_BTN =
'[data-test-subj="sharedListOverflowCardActionItemExport"]'; '[data-test-subj="sharedListOverflowCardActionItemExport"]';
@ -149,6 +152,13 @@ export const CREATE_SHARED_EXCEPTION_LIST_DESCRIPTION_INPUT =
export const CREATE_SHARED_EXCEPTION_LIST_BTN = export const CREATE_SHARED_EXCEPTION_LIST_BTN =
'button[data-test-subj="exception-lists-form-create-shared"]'; 'button[data-test-subj="exception-lists-form-create-shared"]';
export const exceptionsTableListManagementListContainerByListId = (listId: string) =>
`[data-test-subj="exceptionsManagementListCard-${listId}"]`;
export const LINKED_RULES_BADGE = '[data-test-subj="exceptionListCardLinkedRulesBadge"]';
export const MANAGE_RULES_SAVE = '[data-test-subj="manageListRulesSaveButton"]';
// Exception list management // Exception list management
export const EXCEPTIONS_LIST_MANAGEMENT_NAME = export const EXCEPTIONS_LIST_MANAGEMENT_NAME =
'[data-test-subj="exceptionListManagementTitleText"]'; '[data-test-subj="exceptionListManagementTitleText"]';

View file

@ -26,6 +26,11 @@ import {
EXCEPTIONS_LIST_MANAGEMENT_EDIT_MODAL_DESCRIPTION_INPUT, EXCEPTIONS_LIST_MANAGEMENT_EDIT_MODAL_DESCRIPTION_INPUT,
EXCEPTIONS_LIST_EDIT_DETAILS_SAVE_BTN, EXCEPTIONS_LIST_EDIT_DETAILS_SAVE_BTN,
EXCEPTIONS_LIST_DETAILS_HEADER, EXCEPTIONS_LIST_DETAILS_HEADER,
exceptionsTableListManagementListContainerByListId,
EXCEPTIONS_TABLE_LINK_RULES_BTN,
RULE_ACTION_LINK_RULE_SWITCH,
LINKED_RULES_BADGE,
MANAGE_RULES_SAVE,
} from '../screens/exceptions'; } from '../screens/exceptions';
export const clearSearchSelection = () => { export const clearSearchSelection = () => {
@ -36,12 +41,30 @@ export const expandExceptionActions = () => {
cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click();
}; };
export const exportExceptionList = () => { export const exportExceptionList = (listId: string) => {
cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); cy.get(exceptionsTableListManagementListContainerByListId(listId))
.find(EXCEPTIONS_OVERFLOW_ACTIONS_BTN)
.click();
cy.get(EXCEPTIONS_TABLE_EXPORT_MODAL_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_EXPORT_MODAL_BTN).first().click();
cy.get(EXCEPTIONS_TABLE_EXPORT_CONFIRM_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_EXPORT_CONFIRM_BTN).first().click();
}; };
export const assertNumberLinkedRules = (listId: string, numberOfRulesAsString: string) => {
cy.get(exceptionsTableListManagementListContainerByListId(listId))
.find(LINKED_RULES_BADGE)
.contains(numberOfRulesAsString);
};
export const linkRulesToExceptionList = (listId: string, ruleSwitch: number = 0) => {
cy.log(`Open link rules flyout for list_id: '${listId}'`);
cy.get(exceptionsTableListManagementListContainerByListId(listId))
.find(EXCEPTIONS_OVERFLOW_ACTIONS_BTN)
.click();
cy.get(EXCEPTIONS_TABLE_LINK_RULES_BTN).first().click();
cy.get(RULE_ACTION_LINK_RULE_SWITCH).eq(ruleSwitch).find('button').click();
cy.get(MANAGE_RULES_SAVE).first().click();
};
export const deleteExceptionListWithoutRuleReference = () => { export const deleteExceptionListWithoutRuleReference = () => {
cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click();
cy.get(EXCEPTIONS_TABLE_DELETE_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_DELETE_BTN).first().click();
@ -50,8 +73,10 @@ export const deleteExceptionListWithoutRuleReference = () => {
cy.get(EXCEPTIONS_TABLE_MODAL).should('not.exist'); cy.get(EXCEPTIONS_TABLE_MODAL).should('not.exist');
}; };
export const deleteExceptionListWithRuleReference = () => { export const deleteExceptionListWithRuleReferenceByListId = (listId: string) => {
cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).last().click(); cy.get(exceptionsTableListManagementListContainerByListId(listId))
.find(EXCEPTIONS_OVERFLOW_ACTIONS_BTN)
.click();
cy.get(EXCEPTIONS_TABLE_DELETE_BTN).last().click(); cy.get(EXCEPTIONS_TABLE_DELETE_BTN).last().click();
cy.get(EXCEPTIONS_TABLE_MODAL).should('exist'); cy.get(EXCEPTIONS_TABLE_MODAL).should('exist');
cy.get(EXCEPTIONS_TABLE_MODAL_CONFIRM_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_MODAL_CONFIRM_BTN).first().click();

View file

@ -102,7 +102,6 @@ export const ExceptionsListCard = memo<ExceptionsListCardProps>(
toggleAccordion, toggleAccordion,
openAccordionId, openAccordionId,
menuActionItems, menuActionItems,
listRulesCount,
listDescription, listDescription,
exceptionItemsCount, exceptionItemsCount,
onEditExceptionItem, onEditExceptionItem,
@ -184,8 +183,11 @@ export const ExceptionsListCard = memo<ExceptionsListCardProps>(
<EuiFlexItem> <EuiFlexItem>
<TitleBadge title={i18n.EXCEPTIONS} badgeString={exceptionItemsCount} /> <TitleBadge title={i18n.EXCEPTIONS} badgeString={exceptionItemsCount} />
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem> <EuiFlexItem data-test-subj="exceptionListCardLinkedRulesBadge">
<TitleBadge title={i18n.RULES} badgeString={listRulesCount} /> <TitleBadge
title={i18n.RULES}
badgeString={linkedRules.length.toString()}
/>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem> <EuiFlexItem>
<HeaderMenu <HeaderMenu
@ -199,6 +201,7 @@ export const ExceptionsListCard = memo<ExceptionsListCardProps>(
</ListHeaderContainer> </ListHeaderContainer>
</EuiPanel> </EuiPanel>
} }
data-test-subj={`exceptionsManagementListCard-${listId}`}
> >
<ExceptionPanel hasBorder> <ExceptionPanel hasBorder>
<ListExceptionItems <ListExceptionItems

View file

@ -77,6 +77,7 @@ export const ManageRules: FC<ManageRulesProps> = memo(
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiButton <EuiButton
data-test-subj="manageListRulesSaveButton"
isLoading={showButtonLoader} isLoading={showButtonLoader}
disabled={saveIsDisabled} disabled={saveIsDisabled}
onClick={onSave} onClick={onSave}

View file

@ -340,6 +340,9 @@ export const useListDetailsView = (exceptionListId: string) => {
i18n.EXCEPTION_MANAGE_RULES_ERROR_DESCRIPTION i18n.EXCEPTION_MANAGE_RULES_ERROR_DESCRIPTION
); );
setShowManageButtonLoader(false); setShowManageButtonLoader(false);
})
.finally(() => {
initializeList();
}); });
} catch (err) { } catch (err) {
handleErrorStatus(err); handleErrorStatus(err);
@ -348,10 +351,11 @@ export const useListDetailsView = (exceptionListId: string) => {
list, list,
getRulesToAdd, getRulesToAdd,
getRulesToRemove, getRulesToRemove,
exceptionListId,
resetManageRulesAfterSaving, resetManageRulesAfterSaving,
handleErrorStatus, exceptionListId,
invalidateFetchRuleByIdQuery, invalidateFetchRuleByIdQuery,
handleErrorStatus,
initializeList,
]); ]);
const onCancelManageRules = useCallback(() => { const onCancelManageRules = useCallback(() => {
setShowManageRulesFlyout(false); setShowManageRulesFlyout(false);