mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution][Exceptions] - Updates to cypress tests and exceptions viewer (#140448)
## Summary Following up on lingering suggestions from PR 138770 and working to unskip a flakey exceptions cypress test - skipped in PR 140412.
This commit is contained in:
parent
6988f2a974
commit
656f75e896
11 changed files with 94 additions and 61 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -508,6 +508,7 @@ x-pack/examples/files_example @elastic/kibana-app-services
|
|||
/x-pack/plugins/security_solution/cypress/e2e/exceptions @elastic/security-solution-platform
|
||||
/x-pack/plugins/security_solution/cypress/e2e/value_lists @elastic/security-solution-platform
|
||||
|
||||
/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions @elastic/security-solution-platform
|
||||
/x-pack/plugins/security_solution/public/common/components/exceptions @elastic/security-solution-platform
|
||||
/x-pack/plugins/security_solution/public/exceptions @elastic/security-solution-platform
|
||||
/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists @elastic/security-solution-platform
|
||||
|
|
|
@ -16,7 +16,7 @@ import { login, visitWithoutDateRange } from '../../tasks/login';
|
|||
import {
|
||||
openExceptionFlyoutFromEmptyViewerPrompt,
|
||||
goToExceptionsTab,
|
||||
editException,
|
||||
openEditException,
|
||||
} from '../../tasks/rule_details';
|
||||
import {
|
||||
addExceptionEntryFieldMatchAnyValue,
|
||||
|
@ -278,7 +278,7 @@ describe('Exceptions flyout', () => {
|
|||
});
|
||||
|
||||
describe('flyout errors', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
// create exception item via api
|
||||
createExceptionListItem(getExceptionList().list_id, {
|
||||
list_id: getExceptionList().list_id,
|
||||
|
@ -303,9 +303,9 @@ describe('Exceptions flyout', () => {
|
|||
goToExceptionsTab();
|
||||
});
|
||||
|
||||
context.skip('When updating an item with version conflict', () => {
|
||||
context('When updating an item with version conflict', () => {
|
||||
it('Displays version conflict error', () => {
|
||||
editException();
|
||||
openEditException();
|
||||
|
||||
// update exception item via api
|
||||
updateExceptionListItem('simple_list_item', {
|
||||
|
@ -334,9 +334,9 @@ describe('Exceptions flyout', () => {
|
|||
});
|
||||
});
|
||||
|
||||
context.skip('When updating an item for a list that has since been deleted', () => {
|
||||
context('When updating an item for a list that has since been deleted', () => {
|
||||
it('Displays missing exception list error', () => {
|
||||
editException();
|
||||
openEditException();
|
||||
|
||||
// delete exception list via api
|
||||
deleteExceptionList(getExceptionList().list_id, getExceptionList().namespace_type);
|
||||
|
|
|
@ -121,8 +121,8 @@ describe('Add exception from rule details', () => {
|
|||
|
||||
it('Creates an exception item', () => {
|
||||
// displays existing exception items
|
||||
cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
|
||||
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2);
|
||||
cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
|
||||
|
||||
// clicks prompt button to add a new exception item
|
||||
addExceptionFromRuleDetails(getException());
|
||||
|
@ -132,10 +132,10 @@ describe('Add exception from rule details', () => {
|
|||
});
|
||||
|
||||
// Trying to figure out with EUI why the search won't trigger
|
||||
it.skip('Can search for items', () => {
|
||||
it('Can search for items', () => {
|
||||
// displays existing exception items
|
||||
cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
|
||||
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2);
|
||||
cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
|
||||
|
||||
// can search for an exception value
|
||||
searchForExceptionItem('foo');
|
||||
|
|
|
@ -20,14 +20,13 @@ import { login, visitWithoutDateRange } from '../../../tasks/login';
|
|||
import {
|
||||
goToExceptionsTab,
|
||||
waitForTheRuleToBeExecuted,
|
||||
editException,
|
||||
openEditException,
|
||||
goToAlertsTab,
|
||||
} from '../../../tasks/rule_details';
|
||||
|
||||
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation';
|
||||
import { deleteAlertsAndRules } from '../../../tasks/common';
|
||||
import {
|
||||
EXCEPTION_EDIT_FLYOUT_SAVE_BTN,
|
||||
EXCEPTION_ITEM_VIEWER_CONTAINER,
|
||||
EXCEPTION_ITEM_CONTAINER,
|
||||
FIELD_INPUT,
|
||||
|
@ -38,15 +37,14 @@ import {
|
|||
deleteExceptionList,
|
||||
} from '../../../tasks/api_calls/exceptions';
|
||||
import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule';
|
||||
import {
|
||||
addExceptionEntryFieldValueOfItemX,
|
||||
addExceptionEntryFieldValueValue,
|
||||
} from '../../../tasks/exceptions';
|
||||
import { editException } from '../../../tasks/exceptions';
|
||||
import { ALERTS_COUNT, NUMBER_OF_ALERTS } from '../../../screens/alerts';
|
||||
|
||||
describe('Edit exception from rule details', () => {
|
||||
const exceptionList = getExceptionList();
|
||||
const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert';
|
||||
const ITEM_FIELD = 'unique_value.test';
|
||||
const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name';
|
||||
|
||||
before(() => {
|
||||
esArchiverResetKibana();
|
||||
|
@ -90,7 +88,7 @@ describe('Edit exception from rule details', () => {
|
|||
namespace_type: 'single',
|
||||
entries: [
|
||||
{
|
||||
field: 'unique_value.test',
|
||||
field: ITEM_FIELD,
|
||||
operator: 'included',
|
||||
type: 'match_any',
|
||||
value: ['bar'],
|
||||
|
@ -114,22 +112,13 @@ describe('Edit exception from rule details', () => {
|
|||
// displays existing exception item
|
||||
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
|
||||
|
||||
editException();
|
||||
openEditException();
|
||||
|
||||
// check that the existing item's field is being populated
|
||||
cy.get(EXCEPTION_ITEM_CONTAINER)
|
||||
.eq(0)
|
||||
.find(FIELD_INPUT)
|
||||
.eq(0)
|
||||
.should('have.text', 'unique_value.test');
|
||||
cy.get(EXCEPTION_ITEM_CONTAINER).eq(0).find(FIELD_INPUT).eq(0).should('have.text', ITEM_FIELD);
|
||||
|
||||
// check that you can select a different field
|
||||
addExceptionEntryFieldValueOfItemX('agent.name{downarrow}{enter}', 0, 0);
|
||||
addExceptionEntryFieldValueValue('foo', 0);
|
||||
|
||||
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).click();
|
||||
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).should('have.attr', 'disabled');
|
||||
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).should('not.exist');
|
||||
editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0);
|
||||
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
|
||||
|
||||
// Alerts table should still show single alert
|
||||
|
|
|
@ -20,14 +20,13 @@ import { login, visitWithoutDateRange } from '../../../tasks/login';
|
|||
import {
|
||||
goToExceptionsTab,
|
||||
waitForTheRuleToBeExecuted,
|
||||
editException,
|
||||
openEditException,
|
||||
goToAlertsTab,
|
||||
} from '../../../tasks/rule_details';
|
||||
|
||||
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation';
|
||||
import { postDataView, deleteAlertsAndRules } from '../../../tasks/common';
|
||||
import {
|
||||
EXCEPTION_EDIT_FLYOUT_SAVE_BTN,
|
||||
EXCEPTION_ITEM_VIEWER_CONTAINER,
|
||||
EXCEPTION_ITEM_CONTAINER,
|
||||
FIELD_INPUT,
|
||||
|
@ -38,15 +37,14 @@ import {
|
|||
deleteExceptionList,
|
||||
} from '../../../tasks/api_calls/exceptions';
|
||||
import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule';
|
||||
import {
|
||||
addExceptionEntryFieldValueOfItemX,
|
||||
addExceptionEntryFieldValueValue,
|
||||
} from '../../../tasks/exceptions';
|
||||
import { editException } from '../../../tasks/exceptions';
|
||||
import { ALERTS_COUNT, NUMBER_OF_ALERTS } from '../../../screens/alerts';
|
||||
|
||||
describe('Edit exception using data views from rule details', () => {
|
||||
const exceptionList = getExceptionList();
|
||||
const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert';
|
||||
const ITEM_FIELD = 'unique_value.test';
|
||||
const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name';
|
||||
|
||||
before(() => {
|
||||
esArchiverResetKibana();
|
||||
|
@ -91,7 +89,7 @@ describe('Edit exception using data views from rule details', () => {
|
|||
namespace_type: 'single',
|
||||
entries: [
|
||||
{
|
||||
field: 'unique_value.test',
|
||||
field: ITEM_FIELD,
|
||||
operator: 'included',
|
||||
type: 'match_any',
|
||||
value: ['bar'],
|
||||
|
@ -115,22 +113,13 @@ describe('Edit exception using data views from rule details', () => {
|
|||
// displays existing exception item
|
||||
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
|
||||
|
||||
editException();
|
||||
openEditException();
|
||||
|
||||
// check that the existing item's field is being populated
|
||||
cy.get(EXCEPTION_ITEM_CONTAINER)
|
||||
.eq(0)
|
||||
.find(FIELD_INPUT)
|
||||
.eq(0)
|
||||
.should('have.text', 'unique_value.test');
|
||||
cy.get(EXCEPTION_ITEM_CONTAINER).eq(0).find(FIELD_INPUT).eq(0).should('have.text', ITEM_FIELD);
|
||||
|
||||
// check that you can select a different field
|
||||
addExceptionEntryFieldValueOfItemX('agent.name{downarrow}{enter}', 0, 0);
|
||||
addExceptionEntryFieldValueValue('foo', 0);
|
||||
|
||||
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).click();
|
||||
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).should('have.attr', 'disabled');
|
||||
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).should('not.exist');
|
||||
editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0);
|
||||
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
|
||||
|
||||
// Alerts table should still show single alert
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
EXCEPTION_FLYOUT_TITLE,
|
||||
VALUES_INPUT,
|
||||
VALUES_MATCH_ANY_INPUT,
|
||||
EXCEPTION_EDIT_FLYOUT_SAVE_BTN,
|
||||
} from '../screens/exceptions';
|
||||
|
||||
export const addExceptionEntryFieldValueOfItemX = (
|
||||
|
@ -51,3 +52,12 @@ export const addExceptionEntryFieldMatchAnyValue = (value: string, index = 0) =>
|
|||
export const closeExceptionBuilderFlyout = () => {
|
||||
cy.get(CANCEL_BTN).click();
|
||||
};
|
||||
|
||||
export const editException = (updatedField: string, itemIndex = 0, fieldIndex = 0) => {
|
||||
addExceptionEntryFieldValueOfItemX(`${updatedField}{downarrow}{enter}`, itemIndex, fieldIndex);
|
||||
addExceptionEntryFieldValueValue('foo', itemIndex);
|
||||
|
||||
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).click();
|
||||
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).should('have.attr', 'disabled');
|
||||
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).should('not.exist');
|
||||
};
|
||||
|
|
|
@ -129,10 +129,10 @@ export const goToExceptionsTab = () => {
|
|||
cy.get(EXCEPTIONS_TAB).click();
|
||||
};
|
||||
|
||||
export const editException = () => {
|
||||
cy.get(EXCEPTION_ITEM_ACTIONS_BUTTON).eq(0).click({ force: true });
|
||||
export const openEditException = (index = 0) => {
|
||||
cy.get(EXCEPTION_ITEM_ACTIONS_BUTTON).eq(index).click({ force: true });
|
||||
|
||||
cy.get(EDIT_EXCEPTION_BTN).eq(0).click({ force: true });
|
||||
cy.get(EDIT_EXCEPTION_BTN).eq(index).click({ force: true });
|
||||
};
|
||||
|
||||
export const removeException = () => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useEffect, useReducer, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useEffect, useReducer } from 'react';
|
||||
import { EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import type {
|
||||
|
@ -57,6 +57,8 @@ const initialState: State = {
|
|||
exceptionToEdit: null,
|
||||
currenFlyout: null,
|
||||
viewerState: 'loading',
|
||||
isReadOnly: true,
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
|
||||
export interface GetExceptionItemProps {
|
||||
|
@ -79,8 +81,6 @@ const ExceptionsViewerComponent = ({
|
|||
const { services } = useKibana();
|
||||
const toasts = useToasts();
|
||||
const [{ canUserCRUD, hasIndexWrite }] = useUserData();
|
||||
const [isReadOnly, setReadOnly] = useState(true);
|
||||
const [lastUpdated, setLastUpdated] = useState<null | string | number>(null);
|
||||
const exceptionListsToQuery = useMemo(
|
||||
() =>
|
||||
rule != null && rule.exceptions_list != null
|
||||
|
@ -90,12 +90,24 @@ const ExceptionsViewerComponent = ({
|
|||
);
|
||||
|
||||
// Reducer state
|
||||
const [{ exceptions, pagination, currenFlyout, exceptionToEdit, viewerState }, dispatch] =
|
||||
useReducer(allExceptionItemsReducer(), {
|
||||
...initialState,
|
||||
});
|
||||
const [
|
||||
{ exceptions, pagination, currenFlyout, exceptionToEdit, viewerState, isReadOnly, lastUpdated },
|
||||
dispatch,
|
||||
] = useReducer(allExceptionItemsReducer(), {
|
||||
...initialState,
|
||||
});
|
||||
|
||||
// Reducer actions
|
||||
const setLastUpdated = useCallback(
|
||||
(lastUpdate: string | number): void => {
|
||||
dispatch({
|
||||
type: 'setLastUpdateTime',
|
||||
lastUpdate,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const setExceptions = useCallback(
|
||||
({
|
||||
exceptions: newExceptions,
|
||||
|
@ -109,7 +121,7 @@ const ExceptionsViewerComponent = ({
|
|||
pagination: newPagination,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch, setLastUpdated]
|
||||
);
|
||||
|
||||
const setViewerState = useCallback(
|
||||
|
@ -132,6 +144,16 @@ const ExceptionsViewerComponent = ({
|
|||
[dispatch]
|
||||
);
|
||||
|
||||
const setReadOnly = useCallback(
|
||||
(readOnly: boolean): void => {
|
||||
dispatch({
|
||||
type: 'setIsReadOnly',
|
||||
readOnly,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const [_, allReferences] = useFindExceptionListReferences(exceptionListsToQuery);
|
||||
|
||||
const handleFetchItems = useCallback(
|
||||
|
@ -173,7 +195,7 @@ const ExceptionsViewerComponent = ({
|
|||
signal: abortCtrl.signal,
|
||||
});
|
||||
|
||||
// Please see `x-pack/plugins/lists/public/exceptions/transforms.ts` doc notes
|
||||
// Please see `kbn-securitysolution-list-hooks/src/transforms/index.test.ts` doc notes
|
||||
// for context around the temporary `id`
|
||||
const transformedData = data.map((item) => transformInput(item));
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ export interface State {
|
|||
// Flyout to be opened (edit vs add vs none)
|
||||
currenFlyout: ViewerFlyoutName;
|
||||
viewerState: ViewerState;
|
||||
isReadOnly: boolean;
|
||||
lastUpdated: string | number;
|
||||
}
|
||||
|
||||
export type Action =
|
||||
|
@ -43,6 +45,14 @@ export type Action =
|
|||
| {
|
||||
type: 'setViewerState';
|
||||
state: ViewerState;
|
||||
}
|
||||
| {
|
||||
type: 'setIsReadOnly';
|
||||
readOnly: boolean;
|
||||
}
|
||||
| {
|
||||
type: 'setLastUpdateTime';
|
||||
lastUpdate: string | number;
|
||||
};
|
||||
|
||||
export const allExceptionItemsReducer =
|
||||
|
@ -82,6 +92,18 @@ export const allExceptionItemsReducer =
|
|||
viewerState: action.state,
|
||||
};
|
||||
}
|
||||
case 'setIsReadOnly': {
|
||||
return {
|
||||
...state,
|
||||
isReadOnly: action.readOnly,
|
||||
};
|
||||
}
|
||||
case 'setLastUpdateTime': {
|
||||
return {
|
||||
...state,
|
||||
lastUpdated: action.lastUpdate,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ const ExceptionsViewerSearchBarComponent = ({
|
|||
<EuiSearchBar
|
||||
box={{
|
||||
placeholder: 'Search on the fields below: e.g. name:"my list"',
|
||||
incremental: false,
|
||||
incremental: true,
|
||||
schema: ITEMS_SCHEMA,
|
||||
'data-test-subj': 'exceptionsViewerSearchBar',
|
||||
}}
|
||||
|
|
|
@ -35,7 +35,7 @@ const StyledCondition = styled.span`
|
|||
interface ExceptionsViewerUtilityProps {
|
||||
pagination: ExceptionsPagination;
|
||||
// Corresponds to last time exception items were fetched
|
||||
lastUpdated: string | number | null;
|
||||
lastUpdated: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue