From 1b688fe5a4e2e52acda425d3a23421d150fba17c Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Fri, 24 Mar 2023 18:54:48 +0100 Subject: [PATCH 01/64] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- public/app.scss | 3 +- .../containers/CreateDetector.tsx | 11 +- .../containers/Detector/DetectorDetails.tsx | 148 +-------- public/pages/Main/Main.tsx | 14 + public/pages/Main/components/Callout.scss | 10 + public/pages/Main/components/Callout.tsx | 107 ++++++ public/store/DataStore.ts | 6 +- public/store/DetectorsStore.ts | 79 ----- public/store/DetectorsStore.tsx | 312 ++++++++++++++++++ 9 files changed, 470 insertions(+), 220 deletions(-) create mode 100644 public/pages/Main/components/Callout.scss create mode 100644 public/pages/Main/components/Callout.tsx delete mode 100644 public/store/DetectorsStore.ts create mode 100644 public/store/DetectorsStore.tsx diff --git a/public/app.scss b/public/app.scss index ffbc4e3a0..f0391769b 100644 --- a/public/app.scss +++ b/public/app.scss @@ -13,6 +13,7 @@ $euiTextColor: $euiColorDarkestShade !default; @import "./components/Charts/ChartContainer.scss"; @import "./pages/Overview/components/Widgets/WidgetContainer.scss"; +@import "./pages/Main/components/Callout.scss"; .selected-radio-panel { background-color: tintOrShade($euiColorPrimary, 90%, 70%); @@ -119,4 +120,4 @@ $euiTextColor: $euiColorDarkestShade !default; .sa-overview-widget-empty tbody > .euiTableRow > .euiTableRowCell { border-bottom: none; -} +} diff --git a/public/pages/CreateDetector/containers/CreateDetector.tsx b/public/pages/CreateDetector/containers/CreateDetector.tsx index e71a2b1d5..8aaace819 100644 --- a/public/pages/CreateDetector/containers/CreateDetector.tsx +++ b/public/pages/CreateDetector/containers/CreateDetector.tsx @@ -125,10 +125,13 @@ export default class CreateDetector extends Component { - return this.props.savedObjectsService - .createSavedObject(detectorName, logType, detectorId, inputIndices) - .catch((error: any) => { - console.error(error); - }); - }; - getPendingDetector = async () => { - const pendingState = DataStore.detectors.getPendingState(); + const pendingState = DataStore.detectors.getState(); const detector = pendingState?.detectorState?.detector; const pendingRequests = pendingState?.pendingRequests; this.getTabs(); @@ -226,92 +205,30 @@ export class DetectorDetails extends React.Component, - ServerResponse - ]; - - if (mappingsResponse.ok) { - if (detectorResponse.ok) { - let dashboardId; - const detectorId = detectorResponse.response._id; - if (logTypesWithDashboards.has(detector.detector_type)) { - const dashboardResponse = await this.createDashboard( - detector.name, - detector.detector_type, - detectorResponse.response._id, - detector.inputs[0].detector_input.indices - ); - if (dashboardResponse && dashboardResponse.ok) { - dashboardId = dashboardResponse.response.id; - } else { - const dashboards = await this.props.savedObjectsService.getDashboards(); - dashboards.some((dashboard) => { - if ( - dashboard.references.findIndex( - (reference) => reference.id === this.state.detectorId - ) > -1 - ) { - dashboardId = dashboard.id; - return true; - } - - return false; - }); - } - } + const pendingResponse = await DataStore.detectors.getPendingState(); + if (pendingResponse.ok) { + const { detectorId, dashboardId } = pendingResponse; + + detectorId && this.setState( { detectorId, dashboardId, }, () => { - DataStore.detectors.deletePendingState(); + DataStore.detectors.deleteState(); this.props.history.push(`${ROUTES.DETECTOR_DETAILS}/${detectorId}`); this.getDetector(); } ); - - successNotificationToast( - this.props.notifications, - 'created', - `detector, "${detector.name}"` - ); - } else { - this.setState( - { - createFailed: true, - }, - () => - errorNotificationToast( - this.props.notifications, - 'create', - 'detector', - detectorResponse.error - ) - ); - } - } else { - this.setState( - { - createFailed: true, - }, - () => - errorNotificationToast( - this.props.notifications, - 'create', - 'detector', - 'Double check the field mappings and try again.' - ) - ); } } this.setState({ loading: false }); }; async componentDidMount() { - const pendingState = DataStore.detectors.getPendingState(); + const pendingState = DataStore.detectors.getState(); pendingState ? this.getPendingDetector() : this.getDetector(); } @@ -512,17 +429,6 @@ export class DetectorDetails extends React.Component { - const pendingState = DataStore.detectors.getPendingState(); - const detectorState = pendingState?.detectorState; - this.props.history.push({ - pathname: `${ROUTES.DETECTORS_CREATE}`, - // @ts-ignore - state: { detectorState }, - }); - DataStore.detectors.deletePendingState(); - }; - render() { const { _source: detector } = this.detectorHit; const { selectedTabContent, detectorId, createFailed } = this.state; @@ -548,34 +454,6 @@ export class DetectorDetails extends React.Component - {creatingDetector ? ( - <> - - {!createFailed && ( - - - - )} - - {createFailed - ? 'Detector creation failed. Please review detector configuration and try again.' - : 'Attempting to create the detector.'} - - - } - color={createFailed ? 'danger' : 'primary'} - > - {createFailed && ( - - Review detector configuration - - )} - - - - ) : null} diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx index 9c3cad9d0..97840b9ab 100644 --- a/public/pages/Main/Main.tsx +++ b/public/pages/Main/Main.tsx @@ -35,6 +35,8 @@ import { EditRule } from '../Rules/containers/EditRule/EditRule'; import { ImportRule } from '../Rules/containers/ImportRule/ImportRule'; import { DuplicateRule } from '../Rules/containers/DuplicateRule/DuplicateRule'; import { DateTimeFilter } from '../Overview/models/interfaces'; +import Callout, { ICalloutProps } from './components/Callout'; +import { DataStore } from '../../store/DataStore'; enum Navigation { SecurityAnalytics = 'Security Analytics', @@ -68,6 +70,7 @@ interface MainState { getStartedDismissedOnce: boolean; selectedNavItemIndex: number; dateTimeFilter: DateTimeFilter; + callout?: ICalloutProps; } const navItemIndexByRoute: { [route: string]: number } = { @@ -89,8 +92,16 @@ export default class Main extends Component { endTime: DEFAULT_DATE_RANGE.end, }, }; + + DataStore.detectors.setCalloutHandler(this.showCallout); } + showCallout = (callout?: ICalloutProps) => { + this.setState({ + callout, + }); + }; + componentDidMount(): void { this.updateSelectedNavItem(); } @@ -151,6 +162,8 @@ export default class Main extends Component { location: { pathname }, history, } = this.props; + + const { callout } = this.state; const sideNav: EuiSideNavItemType<{ style: any }>[] = [ { name: Navigation.SecurityAnalytics, @@ -230,6 +243,7 @@ export default class Main extends Component { )} + {callout ? : null} void; +} + +export const CallOut = ({ + title, + message, + type, + closable = true, + loading = false, + closeHandler, +}: ICalloutProps) => { + const toastTypes: { + [Key in TCalloutColor]?: TCalloutIcon; + } = { + primary: 'iInCircle', + success: 'check', + warning: 'help', + danger: 'alert', + }; + + const resolveType = (type?: ICalloutType | TCalloutColor | undefined): ICalloutType => { + if (type === undefined) { + return { + color: 'primary', + iconType: 'iInCircle', + }; + } else { + if (typeof type === 'string') { + return { + color: type, + iconType: toastTypes[type], + }; + } else { + return type; + } + } + }; + + const { color, iconType } = resolveType(type); + + const closeCallout = () => closeHandler && closeHandler(undefined); + + const getTitle = (): JSX.Element => { + return ( + + {loading ? ( + + + + ) : null} + {title} + {closable ? ( + + closeCallout()} iconType="cross" aria-label="Close" /> + + ) : null} + + ); + }; + + return ( + <> + + {message ?

{message}

: null} +
+ + + + ); +}; + +export default CallOut; diff --git a/public/store/DataStore.ts b/public/store/DataStore.ts index 9a0687cfd..58a6ad538 100644 --- a/public/store/DataStore.ts +++ b/public/store/DataStore.ts @@ -14,6 +14,10 @@ export class DataStore { public static init = (services: BrowserServices, notifications: NotificationsStart) => { DataStore.rules = new RulesStore(services.ruleService, notifications); - DataStore.detectors = new DetectorsStore(services.detectorsService, notifications); + DataStore.detectors = new DetectorsStore( + services.detectorsService, + notifications, + services.savedObjectsService + ); }; } diff --git a/public/store/DetectorsStore.ts b/public/store/DetectorsStore.ts deleted file mode 100644 index 3a39224dd..000000000 --- a/public/store/DetectorsStore.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DetectorsService } from '../services'; -import { NotificationsStart } from 'opensearch-dashboards/public'; -import { CreateDetectorState } from '../pages/CreateDetector/containers/CreateDetector'; - -export interface IDetectorsStore {} -export interface IDetectorsCache {} - -export interface IDetectorsState { - pendingRequests: Promise[]; - detectorState: CreateDetectorState; -} - -/** - * Class is used to make detector's API calls and cache the detectors. - * If there is a cache data requests are skipped and result is returned from the cache. - * If cache is invalidated then the request is made to get a new set of data. - * - * @class DetectorsStore - * @implements IDetectorsStore - * @param {BrowserServices} services Uses services to make API requests - */ -export class DetectorsStore implements IDetectorsStore { - /** - * Rule service instance - * - * @property {DetectorsService} service - * @readonly - */ - readonly service: DetectorsService; - - /** - * Notifications - * @property {NotificationsStart} - * @readonly - */ - readonly notifications: NotificationsStart; - - /** - * Keeps detector's data cached - * - * @property {IDetectorsCache} cache - */ - private cache: IDetectorsCache = {}; - - private state: IDetectorsState | undefined; - - constructor(service: DetectorsService, notifications: NotificationsStart) { - this.service = service; - this.notifications = notifications; - } - - /** - * Invalidates all detectors data - */ - private invalidateCache = () => { - this.cache = {}; - return this; - }; - - public setPendingState = (state: IDetectorsState) => { - this['state'] = state; - }; - - public getPendingState = () => { - if (!this.state) return undefined; - return { - ...this.state, - }; - }; - - public deletePendingState = () => { - delete this.state; - }; -} diff --git a/public/store/DetectorsStore.tsx b/public/store/DetectorsStore.tsx new file mode 100644 index 000000000..580bd2e6f --- /dev/null +++ b/public/store/DetectorsStore.tsx @@ -0,0 +1,312 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { DetectorsService } from '../services'; +import { NotificationsStart } from 'opensearch-dashboards/public'; +import { CreateDetectorState } from '../pages/CreateDetector/containers/CreateDetector'; +import { ICalloutProps } from '../pages/Main/components/Callout'; +import { CreateDetectorResponse, ISavedObjectsService, ServerResponse } from '../../types'; +import { CreateMappingsResponse } from '../../server/models/interfaces'; +import { logTypesWithDashboards, ROUTES } from '../utils/constants'; +import { EuiButton } from '@elastic/eui'; +import { RouteComponentProps } from 'react-router-dom'; +import { DataStore } from './DataStore'; + +export interface IDetectorsStore {} +export interface IDetectorsCache {} + +export interface IDetectorsState { + pendingRequests: Promise[]; + detectorState: CreateDetectorState; +} + +/** + * Class is used to make detector's API calls and cache the detectors. + * If there is a cache data requests are skipped and result is returned from the cache. + * If cache is invalidated then the request is made to get a new set of data. + * + * @class DetectorsStore + * @implements IDetectorsStore + * @param {BrowserServices} services Uses services to make API requests + */ +export class DetectorsStore implements IDetectorsStore { + /** + * Rule service instance + * + * @property {DetectorsService} service + * @readonly + */ + readonly service: DetectorsService; + + /** + * Notifications + * @property {NotificationsStart} + * @readonly + */ + readonly notifications: NotificationsStart; + + /** + * SavedObjectsService + * @property {SavedObjectsService} + * @readonly + */ + readonly savedObjectsService: ISavedObjectsService; + + /** + * Router history + * @property {RouteComponentProps['history']} + * @readonly + */ + private history: RouteComponentProps['history'] | undefined = undefined; + + /** + * Keeps detector's data cached + * + * @property {IDetectorsCache} cache + */ + private cache: IDetectorsCache = {}; + + /** + * Store state + * @private {IDetectorsState} + */ + private state: IDetectorsState | undefined; + + constructor( + service: DetectorsService, + notifications: NotificationsStart, + savedObjectsService: ISavedObjectsService + ) { + this.service = service; + this.notifications = notifications; + this.savedObjectsService = savedObjectsService; + } + + /** + * Invalidates all detectors data + */ + private invalidateCache = () => { + this.cache = {}; + return this; + }; + + public setState = async (state: IDetectorsState, history: RouteComponentProps['history']) => { + this.state = state; + this.history = history; + + this.showCallout({ + title: 'Attempting to create the detector.', + loading: true, + }); + }; + + public getState = () => (this.state ? this.state : undefined); + + public deleteState = () => { + delete this.state; + }; + + mountToaster = (component: React.ReactElement) => (container: HTMLElement) => { + ReactDOM.render(component, container); + return () => ReactDOM.unmountComponentAtNode(container); + }; + + viewDetectorConfiguration = () => { + const pendingState = DataStore.detectors.getState(); + const detectorState = pendingState?.detectorState; + this.history?.push({ + pathname: `${ROUTES.DETECTORS_CREATE}`, + // @ts-ignore + state: { detectorState }, + }); + DataStore.detectors.deleteState(); + }; + + public getPendingState = async (): Promise<{ + detectorId?: string; + dashboardId?: string; + ok: boolean; + }> => { + if (this.state?.pendingRequests) { + const [mappingsResponse, detectorResponse] = (await Promise.all( + this.state?.pendingRequests + )) as [ServerResponse, ServerResponse]; + + if (!mappingsResponse.ok) { + const error = { + title: `Create detector failed.`, + message: 'Double check the field mappings and try again.', + }; + this.showCallout({ + ...error, + closeHandler: this.showCallout, + }); + + const toast = this.notifications?.toasts.addDanger({ + title: error.title, + text: this.mountToaster( + <> +

{error.message}

+ { + this.hideCallout(); + DataStore.detectors.viewDetectorConfiguration(); + this.notifications?.toasts.remove(toast); + }} + size="s" + > + Review detector configuration + + + ), + toastLifeTimeMs: 30000, + }); + + return Promise.resolve({ + ok: false, + error: error, + }); + } + + if (!detectorResponse.ok) { + const error = { + title: `Create detector failed.`, + message: detectorResponse.error, + }; + + this.showCallout({ + ...error, + closeHandler: this.showCallout, + }); + + const toast = this.notifications?.toasts.addDanger({ + title: error.title, + text: this.mountToaster( + <> +

{error.message}

+ { + this.hideCallout(); + DataStore.detectors.viewDetectorConfiguration(); + this.notifications?.toasts.remove(toast); + }} + size="s" + > + Review detector configuration + + + ), + toastLifeTimeMs: 30000, + }); + + return Promise.resolve({ + ok: false, + error: error, + }); + } + + let dashboardId; + const detector = detectorResponse.response.detector; + const detectorId = detectorResponse.response._id; + if (logTypesWithDashboards.has(detector.detector_type)) { + const dashboardResponse = await this.createDashboard( + detector.name, + detector.detector_type, + detectorId, + detector.inputs[0].detector_input.indices + ); + if (dashboardResponse && dashboardResponse.ok) { + dashboardId = dashboardResponse.response.id; + } else { + const dashboards = await this.savedObjectsService.getDashboards(); + dashboards.some((dashboard) => { + if (dashboard.references.findIndex((reference) => reference.id === detectorId) > -1) { + dashboardId = dashboard.id; + return true; + } + + return false; + }); + } + } + + const success = { + title: `Detector created successfully: ${detectorResponse.response.detector.name}`, + }; + this.showCallout({ + type: 'success', + ...success, + closeHandler: this.showCallout, + }); + + const toast = this.notifications?.toasts.addSuccess({ + title: success.title, + text: this.mountToaster( + { + this.hideCallout(); + DataStore.detectors.deleteState(); + this.history?.push(`${ROUTES.DETECTOR_DETAILS}/${detectorId}`); + this.notifications?.toasts.remove(toast); + }} + size="s" + > + View detector + + ), + toastLifeTimeMs: 30000, + }); + + return Promise.resolve({ + detectorId: detectorId, + dashboardId: dashboardId, + ok: true, + }); + } + + return Promise.resolve({ ok: false }); + }; + + showNotification = ( + title: string, + message: string, + loading: boolean, + type: string = 'success', + btnText: string, + btnHandler: () => void + ) => { + this.showCallout({ + type, + title, + message, + closeHandler: this.showCallout, + }); + }; + + private createDashboard = ( + detectorName: string, + logType: string, + detectorId: string, + inputIndices: string[] + ) => { + return this.savedObjectsService + .createSavedObject(detectorName, logType, detectorId, inputIndices) + .catch((error: any) => { + console.error(error); + }); + }; + + showCallout = (callout?: ICalloutProps | undefined): void => {}; + + hideCallout = () => this.showCallout(undefined); + + public setCalloutHandler = (calloutHandler: (callout?: ICalloutProps) => void) => { + this.showCallout = calloutHandler; + }; +} From 9daf6d9c50122f126985eab5a1e6a972c3c63287 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sun, 26 Mar 2023 20:20:45 +0200 Subject: [PATCH 02/64] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- public/pages/Main/components/Callout.tsx | 14 +- public/store/DetectorsStore.tsx | 249 ++++++++++++----------- 2 files changed, 136 insertions(+), 127 deletions(-) diff --git a/public/pages/Main/components/Callout.tsx b/public/pages/Main/components/Callout.tsx index 7d2426f76..6eb3da5b9 100644 --- a/public/pages/Main/components/Callout.tsx +++ b/public/pages/Main/components/Callout.tsx @@ -13,8 +13,8 @@ import { EuiLoadingSpinner, } from '@elastic/eui'; -export type TCalloutColor = 'primary' | 'success' | 'warning' | 'danger' | undefined; -export type TCalloutIcon = 'iInCircle' | 'check' | 'help' | 'alert' | undefined; +export type TCalloutColor = 'primary' | 'success' | 'warning' | 'danger'; +export type TCalloutIcon = 'iInCircle' | 'check' | 'help' | 'alert'; interface ICalloutType { color: TCalloutColor; @@ -22,10 +22,9 @@ interface ICalloutType { } export interface ICalloutProps { - title: string; + title: string | JSX.Element; message?: string | JSX.Element; type?: ICalloutType | TCalloutColor; - closable?: boolean; loading?: boolean; closeHandler?: (callout?: ICalloutProps) => void; @@ -40,7 +39,7 @@ export const CallOut = ({ closeHandler, }: ICalloutProps) => { const toastTypes: { - [Key in TCalloutColor]?: TCalloutIcon; + [Key in TCalloutColor]: TCalloutIcon; } = { primary: 'iInCircle', success: 'check', @@ -48,7 +47,7 @@ export const CallOut = ({ danger: 'alert', }; - const resolveType = (type?: ICalloutType | TCalloutColor | undefined): ICalloutType => { + const resolveType = (type?: ICalloutType | TCalloutColor): ICalloutType => { if (type === undefined) { return { color: 'primary', @@ -66,8 +65,6 @@ export const CallOut = ({ } }; - const { color, iconType } = resolveType(type); - const closeCallout = () => closeHandler && closeHandler(undefined); const getTitle = (): JSX.Element => { @@ -88,6 +85,7 @@ export const CallOut = ({ ); }; + const { color, iconType } = resolveType(type); return ( <> void; + getState: () => IDetectorsState | undefined; + deleteState: () => void; + getPendingState: () => Promise<{ + detectorId?: string; + dashboardId?: string; + ok: boolean; + }>; + setCalloutHandler: (calloutHandler: (callout?: ICalloutProps) => void) => void; +} + export interface IDetectorsCache {} export interface IDetectorsState { @@ -51,7 +66,7 @@ export class DetectorsStore implements IDetectorsStore { /** * SavedObjectsService - * @property {SavedObjectsService} + * @property {ISavedObjectsService} * @readonly */ readonly savedObjectsService: ISavedObjectsService; @@ -61,7 +76,7 @@ export class DetectorsStore implements IDetectorsStore { * @property {RouteComponentProps['history']} * @readonly */ - private history: RouteComponentProps['history'] | undefined = undefined; + history: RouteComponentProps['history'] | undefined = undefined; /** * Keeps detector's data cached @@ -89,38 +104,98 @@ export class DetectorsStore implements IDetectorsStore { /** * Invalidates all detectors data */ - private invalidateCache = () => { + private invalidateCache = (): DetectorsStore => { this.cache = {}; return this; }; - public setState = async (state: IDetectorsState, history: RouteComponentProps['history']) => { + public setState = (state: IDetectorsState, history: RouteComponentProps['history']): void => { this.state = state; this.history = history; - this.showCallout({ - title: 'Attempting to create the detector.', - loading: true, - }); + this.showNotification('Attempting to create the detector.', undefined, 'primary', true); }; - public getState = () => (this.state ? this.state : undefined); + public getState = (): IDetectorsState | undefined => (this.state ? this.state : undefined); - public deleteState = () => { + public deleteState = (): void => { delete this.state; }; - mountToaster = (component: React.ReactElement) => (container: HTMLElement) => { + private showNotification = ( + title: string, + message?: string, + type?: TCalloutColor, + loading?: boolean, + btnText?: string, + btnHandler?: (e: any) => void + ): void => { + if (!type) type = 'primary'; + + const btn = btnText ? ( + { + btnHandler && btnHandler(e); + this.hideCallout(); + this.notifications?.toasts.remove(toast); + }} + size="s" + > + {btnText} + + ) : null; + + this.showCallout({ + type, + title, + message: ( + <> + {message} + {btn} + + ), + closeHandler: this.showCallout, + }); + + let toastGenerator; + switch (type) { + case 'danger': + toastGenerator = this.notifications?.toasts.addDanger; + break; + case 'warning': + toastGenerator = this.notifications?.toasts.addWarning; + break; + case 'success': + toastGenerator = this.notifications?.toasts.addSuccess; + break; + default: + toastGenerator = this.notifications?.toasts.addInfo; + break; + } + + const toast = toastGenerator.bind(this.notifications?.toasts)({ + title: title, + text: this.mountToaster( + <> + {message ?

{message}

: null} + {btn} + + ), + toastLifeTimeMs: 5000, + }); + }; + + private mountToaster = (component: React.ReactElement) => (container: HTMLElement) => { ReactDOM.render(component, container); return () => ReactDOM.unmountComponentAtNode(container); }; - viewDetectorConfiguration = () => { + private viewDetectorConfiguration = (): void => { const pendingState = DataStore.detectors.getState(); const detectorState = pendingState?.detectorState; this.history?.push({ pathname: `${ROUTES.DETECTORS_CREATE}`, - // @ts-ignore state: { detectorState }, }); DataStore.detectors.deleteState(); @@ -136,78 +211,41 @@ export class DetectorsStore implements IDetectorsStore { this.state?.pendingRequests )) as [ServerResponse, ServerResponse]; + let title: string = `Create detector failed.`; if (!mappingsResponse.ok) { - const error = { - title: `Create detector failed.`, - message: 'Double check the field mappings and try again.', - }; - this.showCallout({ - ...error, - closeHandler: this.showCallout, - }); - - const toast = this.notifications?.toasts.addDanger({ - title: error.title, - text: this.mountToaster( - <> -

{error.message}

- { - this.hideCallout(); - DataStore.detectors.viewDetectorConfiguration(); - this.notifications?.toasts.remove(toast); - }} - size="s" - > - Review detector configuration - - - ), - toastLifeTimeMs: 30000, - }); + const message = 'Double check the field mappings and try again.'; + + this.showNotification( + title, + message, + 'danger', + false, + 'Review detector configuration', + DataStore.detectors.viewDetectorConfiguration + ); return Promise.resolve({ ok: false, - error: error, + error: { title, message }, }); } if (!detectorResponse.ok) { - const error = { - title: `Create detector failed.`, - message: detectorResponse.error, - }; - - this.showCallout({ - ...error, - closeHandler: this.showCallout, - }); - - const toast = this.notifications?.toasts.addDanger({ - title: error.title, - text: this.mountToaster( - <> -

{error.message}

- { - this.hideCallout(); - DataStore.detectors.viewDetectorConfiguration(); - this.notifications?.toasts.remove(toast); - }} - size="s" - > - Review detector configuration - - - ), - toastLifeTimeMs: 30000, - }); + this.showNotification( + title, + detectorResponse.error, + 'danger', + false, + 'Review detector configuration', + DataStore.detectors.viewDetectorConfiguration + ); return Promise.resolve({ ok: false, - error: error, + error: { + title, + message: detectorResponse.error, + }, }); } @@ -236,32 +274,21 @@ export class DetectorsStore implements IDetectorsStore { } } - const success = { - title: `Detector created successfully: ${detectorResponse.response.detector.name}`, + const goToDetectorDetails = (e: any) => { + e.preventDefault(); + DataStore.detectors.deleteState(); + this.history?.push(`${ROUTES.DETECTOR_DETAILS}/${detectorId}`); }; - this.showCallout({ - type: 'success', - ...success, - closeHandler: this.showCallout, - }); - const toast = this.notifications?.toasts.addSuccess({ - title: success.title, - text: this.mountToaster( - { - this.hideCallout(); - DataStore.detectors.deleteState(); - this.history?.push(`${ROUTES.DETECTOR_DETAILS}/${detectorId}`); - this.notifications?.toasts.remove(toast); - }} - size="s" - > - View detector - - ), - toastLifeTimeMs: 30000, - }); + title = `Detector created successfully: ${detectorResponse.response.detector.name}`; + this.showNotification( + title, + undefined, + 'success', + false, + 'View detector', + goToDetectorDetails + ); return Promise.resolve({ detectorId: detectorId, @@ -273,22 +300,6 @@ export class DetectorsStore implements IDetectorsStore { return Promise.resolve({ ok: false }); }; - showNotification = ( - title: string, - message: string, - loading: boolean, - type: string = 'success', - btnText: string, - btnHandler: () => void - ) => { - this.showCallout({ - type, - title, - message, - closeHandler: this.showCallout, - }); - }; - private createDashboard = ( detectorName: string, logType: string, @@ -302,11 +313,11 @@ export class DetectorsStore implements IDetectorsStore { }); }; - showCallout = (callout?: ICalloutProps | undefined): void => {}; + private showCallout = (callout?: ICalloutProps | undefined): void => {}; - hideCallout = () => this.showCallout(undefined); + private hideCallout = (): void => this.showCallout(undefined); - public setCalloutHandler = (calloutHandler: (callout?: ICalloutProps) => void) => { + public setCalloutHandler = (calloutHandler: (callout?: ICalloutProps) => void): void => { this.showCallout = calloutHandler; }; } From bb04a080bdd02c582a21f233f070fc00960d3b14 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sun, 26 Mar 2023 21:13:07 +0200 Subject: [PATCH 03/64] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- .../Alerts/__snapshots__/Alerts.test.tsx.snap | 7 ++ .../DetectorRulesView.test.tsx.snap | 3 + .../UpdateAlertConditions.test.tsx.snap | 6 + .../UpdateDetectorBasicDetails.test.tsx.snap | 7 ++ .../AlertTriggersView.test.tsx.snap | 3 + .../DetectorDetails.test.tsx.snap | 16 +++ .../DetectorDetailsView.test.tsx.snap | 9 ++ .../__snapshots__/Detectors.test.tsx.snap | 3 + public/store/DetectorsStore.test.ts | 103 ++++++++++++++++++ public/store/DetectorsStore.tsx | 1 + test/mocks/services/browserHistory.mock.ts | 1 + .../notifications/NotificationsStart.mock.ts | 3 + 12 files changed, 162 insertions(+) create mode 100644 public/store/DetectorsStore.test.ts diff --git a/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap b/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap index 050feb771..5f531740b 100644 --- a/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap +++ b/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap @@ -49,6 +49,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } @@ -138,6 +141,7 @@ exports[` spec renders the component 1`] = ` "location": Object { "pathname": "", }, + "push": [MockFunction], "replace": [MockFunction], } } @@ -158,6 +162,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } diff --git a/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap b/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap index 6598f2bc1..6216984cc 100644 --- a/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap +++ b/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap @@ -183,6 +183,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } diff --git a/public/pages/Detectors/components/UpdateAlertConditions/__snapshots__/UpdateAlertConditions.test.tsx.snap b/public/pages/Detectors/components/UpdateAlertConditions/__snapshots__/UpdateAlertConditions.test.tsx.snap index 5c1981715..735f7d5f9 100644 --- a/public/pages/Detectors/components/UpdateAlertConditions/__snapshots__/UpdateAlertConditions.test.tsx.snap +++ b/public/pages/Detectors/components/UpdateAlertConditions/__snapshots__/UpdateAlertConditions.test.tsx.snap @@ -392,6 +392,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } @@ -999,6 +1002,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } diff --git a/public/pages/Detectors/components/UpdateBasicDetails/__snapshots__/UpdateDetectorBasicDetails.test.tsx.snap b/public/pages/Detectors/components/UpdateBasicDetails/__snapshots__/UpdateDetectorBasicDetails.test.tsx.snap index a787a5377..b020ea5a1 100644 --- a/public/pages/Detectors/components/UpdateBasicDetails/__snapshots__/UpdateDetectorBasicDetails.test.tsx.snap +++ b/public/pages/Detectors/components/UpdateBasicDetails/__snapshots__/UpdateDetectorBasicDetails.test.tsx.snap @@ -190,6 +190,7 @@ exports[` spec renders the component 1`] = ` "location": Object { "pathname": "", }, + "push": [MockFunction], "replace": [MockFunction] { "calls": Array [ Array [ @@ -576,6 +577,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } @@ -904,6 +908,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } diff --git a/public/pages/Detectors/containers/AlertTriggersView/__snapshots__/AlertTriggersView.test.tsx.snap b/public/pages/Detectors/containers/AlertTriggersView/__snapshots__/AlertTriggersView.test.tsx.snap index 4b134ef5d..2083bd05c 100644 --- a/public/pages/Detectors/containers/AlertTriggersView/__snapshots__/AlertTriggersView.test.tsx.snap +++ b/public/pages/Detectors/containers/AlertTriggersView/__snapshots__/AlertTriggersView.test.tsx.snap @@ -183,6 +183,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } diff --git a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap index e492bc285..45b6924a6 100644 --- a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap +++ b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap @@ -195,6 +195,7 @@ exports[` spec renders the component 1`] = ` "location": Object { "pathname": "", }, + "push": [MockFunction], "replace": [MockFunction], } } @@ -207,6 +208,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } @@ -887,6 +891,7 @@ exports[` spec renders the component 1`] = ` "location": Object { "pathname": "", }, + "push": [MockFunction], "replace": [MockFunction], } } @@ -901,6 +906,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } @@ -1293,6 +1301,7 @@ exports[` spec renders the component 1`] = ` "location": Object { "pathname": "", }, + "push": [MockFunction], "replace": [MockFunction], } } @@ -1307,6 +1316,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } @@ -2648,6 +2660,7 @@ exports[` spec renders the component 1`] = ` "location": Object { "pathname": "", }, + "push": [MockFunction], "replace": [MockFunction], } } @@ -2662,6 +2675,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } diff --git a/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap b/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap index 3f9cac30e..d59f77923 100644 --- a/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap +++ b/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap @@ -186,6 +186,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } @@ -377,6 +380,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } @@ -1517,6 +1523,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } diff --git a/public/pages/Detectors/containers/Detectors/__snapshots__/Detectors.test.tsx.snap b/public/pages/Detectors/containers/Detectors/__snapshots__/Detectors.test.tsx.snap index 7cf9f7c8f..0a66bbe88 100644 --- a/public/pages/Detectors/containers/Detectors/__snapshots__/Detectors.test.tsx.snap +++ b/public/pages/Detectors/containers/Detectors/__snapshots__/Detectors.test.tsx.snap @@ -23,6 +23,9 @@ exports[` spec renders the component 1`] = ` Object { "toasts": Object { "addDanger": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], }, } } diff --git a/public/store/DetectorsStore.test.ts b/public/store/DetectorsStore.test.ts new file mode 100644 index 000000000..09fc82794 --- /dev/null +++ b/public/store/DetectorsStore.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DataStore } from './DataStore'; +import notificationsStartMock from '../../test/mocks/services/notifications/NotificationsStart.mock'; +import services from '../../test/mocks/services'; +import { DetectorsStore } from './DetectorsStore'; +import { expect } from '@jest/globals'; +import detectorResponseMock from '../../test/mocks/Detectors/containers/Detectors/DetectorResponse.mock'; +import browserHistoryMock from '../../test/mocks/services/browserHistory.mock'; +import { CreateDetectorState } from '../pages/CreateDetector/containers/CreateDetector'; +import DetectorMock from '../../test/mocks/Detectors/containers/Detectors/Detector.mock'; +describe('Detectors store specs', () => { + Object.assign(services, { + detectorService: { + getRules: () => Promise.resolve(detectorResponseMock), + deleteRule: () => Promise.resolve(true), + }, + }); + + DataStore.init(services, notificationsStartMock); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('detectors store should be created', () => { + expect(DataStore.detectors instanceof DetectorsStore).toBe(true); + }); + + it('should handle the state', () => { + DataStore.detectors.setState( + { + pendingRequests: [Promise.resolve()], + detectorState: { + detector: { detector_type: 'test_detector_type' } as typeof DetectorMock, + } as CreateDetectorState, + }, + browserHistoryMock + ); + + let state = DataStore.detectors.getState(); + expect(state?.detectorState?.detector.detector_type).toBe('test_detector_type'); + + DataStore.detectors.deleteState(); + state = DataStore.detectors.getState(); + expect(state).toBe(undefined); + }); + + it('should get successful pending state', async () => { + DataStore.detectors.setState( + { + pendingRequests: [ + Promise.resolve({ + ok: true, + }), + Promise.resolve({ + ok: true, + response: { + _id: '', + detector: { + detector_type: '', + inputs: [ + { + detector_input: { + indices: [], + }, + }, + ], + }, + }, + }), + ], + detectorState: { + detector: { detector_type: 'test_detector_type' } as typeof DetectorMock, + } as CreateDetectorState, + }, + browserHistoryMock + ); + const pending = await DataStore.detectors.getPendingState(); + expect(pending.ok).toBe(true); + }); + + it('should get failed pending state', async () => { + DataStore.detectors.setState( + { + pendingRequests: [ + Promise.resolve({ + ok: false, + }), + ], + detectorState: { + detector: { detector_type: 'test_detector_type' } as typeof DetectorMock, + } as CreateDetectorState, + }, + browserHistoryMock + ); + const pending = await DataStore.detectors.getPendingState(); + expect(pending.ok).toBe(false); + }); +}); diff --git a/public/store/DetectorsStore.tsx b/public/store/DetectorsStore.tsx index 829318de6..ef0ba8cdb 100644 --- a/public/store/DetectorsStore.tsx +++ b/public/store/DetectorsStore.tsx @@ -206,6 +206,7 @@ export class DetectorsStore implements IDetectorsStore { dashboardId?: string; ok: boolean; }> => { + debugger; if (this.state?.pendingRequests) { const [mappingsResponse, detectorResponse] = (await Promise.all( this.state?.pendingRequests diff --git a/test/mocks/services/browserHistory.mock.ts b/test/mocks/services/browserHistory.mock.ts index a40a1a2ff..9af44b50c 100644 --- a/test/mocks/services/browserHistory.mock.ts +++ b/test/mocks/services/browserHistory.mock.ts @@ -9,4 +9,5 @@ export default ({ location: { pathname: '', }, + push: jest.fn(), } as unknown) as History; diff --git a/test/mocks/services/notifications/NotificationsStart.mock.ts b/test/mocks/services/notifications/NotificationsStart.mock.ts index 73f62608c..f1afac33f 100644 --- a/test/mocks/services/notifications/NotificationsStart.mock.ts +++ b/test/mocks/services/notifications/NotificationsStart.mock.ts @@ -8,5 +8,8 @@ import { NotificationsStart } from 'opensearch-dashboards/public'; export default ({ toasts: { addDanger: jest.fn(), + addWarning: jest.fn(), + addSuccess: jest.fn(), + addInfo: jest.fn(), }, } as unknown) as NotificationsStart; From 0d7362e5bf3153ebc2a2e5f0c77cab3ee8c05f70 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sun, 26 Mar 2023 22:34:47 +0200 Subject: [PATCH 04/64] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 270 +++++++++++++----------- 1 file changed, 145 insertions(+), 125 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 092006e45..1514deab8 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -18,6 +18,145 @@ const testMappings = { const cypressDNSRule = dns_rule_data.title; +const createDetector = (detectorName, dataSource, expectFailure) => { + // Locate Create detector button click to start + cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true }); + + // Check to ensure process started + cy.waitForPageLoad('create-detector', { + contains: 'Define detector', + }); + + // Enter a name for the detector in the appropriate input + cy.get(`input[placeholder="Enter a name for the detector."]`).focus().realType(detectorName); + + // Select our pre-seeded data source (check cypressIndexDns) + cy.get(`[data-test-subj="define-detector-select-data-source"]`) + .find('input') + .focus() + .realType(dataSource); + + cy.intercept({ + pathname: '/_plugins/_security_analytics/rules/_search', + query: { + prePackaged: 'true', + }, + }).as('getSigmaRules'); + + // Select threat detector type (Windows logs) + cy.get(`input[id="dns"]`).click({ force: true }); + + cy.wait('@getSigmaRules').then(() => { + // Open Detection rules accordion + cy.get('[data-test-subj="detection-rules-btn"]').click({ force: true, timeout: 5000 }); + + cy.contains('table tr', 'DNS', { + timeout: 120000, + }); + + // find search, type USB + cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); + + // Disable all rules + cy.contains('tr', cypressDNSRule, { timeout: 1000 }); + cy.get('table th').within(() => { + cy.get('button').first().click({ force: true }); + }); + + // Enable single rule + cy.contains('table tr', cypressDNSRule).within(() => { + cy.get('button').eq(1).click({ force: true, timeout: 2000 }); + }); + }); + + // Click Next button to continue + cy.get('button').contains('Next').click({ force: true }); + + // Check that correct page now showing + cy.contains('Configure field mapping'); + + if (!expectFailure) { + // Select appropriate names to map fields to + for (let field_name in testMappings.properties) { + const mappedTo = testMappings.properties[field_name].path; + + cy.contains('tr', field_name).within(() => { + cy.get(`[data-test-subj="detector-field-mappings-select"]`).click().type(mappedTo); + }); + } + } + + // Continue to next page + cy.get('button').contains('Next').click({ force: true, timeout: 2000 }); + + // Check that correct page now showing + cy.contains('Set up alerts'); + + // Type name of new trigger + cy.get(`input[placeholder="Enter a name for the alert condition."]`) + .focus() + .realType('test_trigger'); + + // Type in (or select) tags for the alert condition + cy.get(`[data-test-subj="alert-tags-combo-box"]`) + .find('input') + .focus() + .realType('attack.defense_evasion') + .realPress('Enter'); + + // Select applicable severity levels + cy.get(`[data-test-subj="security-levels-combo-box"]`).click({ force: true }); + cy.contains('1 (Highest)').click({ force: true }); + + // Continue to next page + cy.contains('Next').click({ force: true }); + + // Confirm page is reached + cy.contains('Review and create'); + + // Confirm field mappings registered + cy.contains('Field mapping'); + + if (!expectFailure) { + for (let field in testMappings.properties) { + const mappedTo = testMappings.properties[field].path; + + cy.contains(field); + cy.contains(mappedTo); + } + } + + // Confirm entries user has made + cy.contains('Detector details'); + cy.contains(detectorName); + cy.contains('dns'); + cy.contains(dataSource); + cy.contains('Alert on test_trigger'); + + // Create the detector + cy.get('button').contains('Create').click({ force: true }); + cy.waitForPageLoad('detector-details', { + contains: detectorName, + }); + + cy.contains('Attempting to create the detector.'); + + // Confirm detector active + cy.contains(detectorName); + cy.contains('Active'); + + if (!expectFailure) { + cy.contains('Actions'); + } + + cy.contains('Detector configuration'); + cy.contains('Field mappings'); + cy.contains('Alert triggers'); + cy.contains('Detector details'); + cy.contains('Created at'); + cy.contains('Last updated time'); +}; + describe('Detectors', () => { const cypressIndexDns = 'cypress-index-dns'; const cypressIndexWindows = 'cypress-index-windows'; @@ -90,132 +229,13 @@ describe('Detectors', () => { }); it('...can be created', () => { - // Locate Create detector button click to start - cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true }); - - // Check to ensure process started - cy.waitForPageLoad('create-detector', { - contains: 'Define detector', - }); - - // Enter a name for the detector in the appropriate input - cy.get(`input[placeholder="Enter a name for the detector."]`).focus().realType('test detector'); - - // Select our pre-seeded data source (check cypressIndexDns) - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .focus() - .realType(cypressIndexDns); - - cy.intercept({ - pathname: '/_plugins/_security_analytics/rules/_search', - query: { - prePackaged: 'true', - }, - }).as('getSigmaRules'); - - // Select threat detector type (Windows logs) - cy.get(`input[id="dns"]`).click({ force: true }); - - cy.wait('@getSigmaRules').then(() => { - // Open Detection rules accordion - cy.get('[data-test-subj="detection-rules-btn"]').click({ force: true, timeout: 5000 }); - - cy.contains('table tr', 'DNS', { - timeout: 120000, - }); - - // find search, type USB - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - - // Disable all rules - cy.contains('tr', cypressDNSRule, { timeout: 1000 }); - cy.get('table th').within(() => { - cy.get('button').first().click({ force: true }); - }); - - // Enable single rule - cy.contains('table tr', cypressDNSRule).within(() => { - cy.get('button').eq(1).click({ force: true, timeout: 2000 }); - }); - }); - - // Click Next button to continue - cy.get('button').contains('Next').click({ force: true }); - - // Check that correct page now showing - cy.contains('Configure field mapping'); - - // Select appropriate names to map fields to - for (let field_name in testMappings.properties) { - const mappedTo = testMappings.properties[field_name].path; - - cy.contains('tr', field_name).within(() => { - cy.get(`[data-test-subj="detector-field-mappings-select"]`).click().type(mappedTo); - }); - } - - // Continue to next page - cy.get('button').contains('Next').click({ force: true, timeout: 2000 }); - - // Check that correct page now showing - cy.contains('Set up alerts'); - - // Type name of new trigger - cy.get(`input[placeholder="Enter a name for the alert condition."]`) - .focus() - .realType('test_trigger'); - - // Type in (or select) tags for the alert condition - cy.get(`[data-test-subj="alert-tags-combo-box"]`) - .find('input') - .focus() - .realType('attack.defense_evasion') - .realPress('Enter'); - - // Select applicable severity levels - cy.get(`[data-test-subj="security-levels-combo-box"]`).click({ force: true }); - cy.contains('1 (Highest)').click({ force: true }); - - // Continue to next page - cy.contains('Next').click({ force: true }); - - // Confirm page is reached - cy.contains('Review and create'); - - // Confirm field mappings registered - cy.contains('Field mapping'); - - for (let field in testMappings.properties) { - const mappedTo = testMappings.properties[field].path; - - cy.contains(field); - cy.contains(mappedTo); - } - - // Confirm entries user has made - cy.contains('Detector details'); - cy.contains(detectorName); - cy.contains('dns'); - cy.contains(cypressIndexDns); - cy.contains('Alert on test_trigger'); - - // Create the detector - cy.get('button').contains('Create').click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); + createDetector(detectorName, cypressIndexDns, false); + cy.contains('Detector created successfully'); + }); - // Confirm detector active - cy.contains(detectorName); - cy.contains('Active'); - cy.contains('Actions'); - cy.contains('Detector configuration'); - cy.contains('Field mappings'); - cy.contains('Alert triggers'); - cy.contains('Detector details'); - cy.contains('Created at'); - cy.contains('Last updated time'); + it('...can fail creation', () => { + createDetector(`${detectorName}_fail`, '.kibana_1', true); + cy.contains('Create detector failed.'); }); it('...basic details can be edited', () => { From b952bb3e31f3917ca684c7b2f6a62356195d7e0b Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sun, 26 Mar 2023 22:38:12 +0200 Subject: [PATCH 05/64] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- public/store/DetectorsStore.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/store/DetectorsStore.test.ts b/public/store/DetectorsStore.test.ts index 09fc82794..dc3896e41 100644 --- a/public/store/DetectorsStore.test.ts +++ b/public/store/DetectorsStore.test.ts @@ -12,6 +12,7 @@ import detectorResponseMock from '../../test/mocks/Detectors/containers/Detector import browserHistoryMock from '../../test/mocks/services/browserHistory.mock'; import { CreateDetectorState } from '../pages/CreateDetector/containers/CreateDetector'; import DetectorMock from '../../test/mocks/Detectors/containers/Detectors/Detector.mock'; + describe('Detectors store specs', () => { Object.assign(services, { detectorService: { From 3d2a1f374da4cfba285333f05e37d4ebe99ac2bd Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sun, 26 Mar 2023 22:44:11 +0200 Subject: [PATCH 06/64] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- public/store/DetectorsStore.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/public/store/DetectorsStore.tsx b/public/store/DetectorsStore.tsx index ef0ba8cdb..829318de6 100644 --- a/public/store/DetectorsStore.tsx +++ b/public/store/DetectorsStore.tsx @@ -206,7 +206,6 @@ export class DetectorsStore implements IDetectorsStore { dashboardId?: string; ok: boolean; }> => { - debugger; if (this.state?.pendingRequests) { const [mappingsResponse, detectorResponse] = (await Promise.all( this.state?.pendingRequests From 4e0b834624a1385f5e1154d3f7675ffd57b40b74 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sun, 26 Mar 2023 23:13:48 +0200 Subject: [PATCH 07/64] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- public/store/DetectorsStore.tsx | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/public/store/DetectorsStore.tsx b/public/store/DetectorsStore.tsx index 829318de6..daca1a244 100644 --- a/public/store/DetectorsStore.tsx +++ b/public/store/DetectorsStore.tsx @@ -12,7 +12,7 @@ import { ICalloutProps, TCalloutColor } from '../pages/Main/components/Callout'; import { CreateDetectorResponse, ISavedObjectsService, ServerResponse } from '../../types'; import { CreateMappingsResponse } from '../../server/models/interfaces'; import { logTypesWithDashboards, ROUTES } from '../utils/constants'; -import { EuiButton } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { RouteComponentProps } from 'react-router-dom'; import { DataStore } from './DataStore'; @@ -146,15 +146,17 @@ export class DetectorsStore implements IDetectorsStore { ) : null; + const messageBody = ( + + {message ?

{message}

: null}
+ {btn} +
+ ); + this.showCallout({ type, title, - message: ( - <> - {message} - {btn} - - ), + message: messageBody, closeHandler: this.showCallout, }); @@ -176,12 +178,7 @@ export class DetectorsStore implements IDetectorsStore { const toast = toastGenerator.bind(this.notifications?.toasts)({ title: title, - text: this.mountToaster( - <> - {message ?

{message}

: null} - {btn} - - ), + text: this.mountToaster(messageBody), toastLifeTimeMs: 5000, }); }; From ee8afd9252b07060cb698792f2ee3bdbdfa757aa Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 27 Mar 2023 09:11:32 +0200 Subject: [PATCH 08/64] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- public/pages/Main/components/Callout.tsx | 10 +++---- public/store/DetectorsStore.tsx | 35 +++++++++++++++++------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/public/pages/Main/components/Callout.tsx b/public/pages/Main/components/Callout.tsx index 6eb3da5b9..1213dbd0b 100644 --- a/public/pages/Main/components/Callout.tsx +++ b/public/pages/Main/components/Callout.tsx @@ -70,17 +70,17 @@ export const CallOut = ({ const getTitle = (): JSX.Element => { return ( - {loading ? ( + {loading && ( - ) : null} + )} {title} - {closable ? ( + {closable && ( closeCallout()} iconType="cross" aria-label="Close" /> - ) : null} + )} ); }; @@ -94,7 +94,7 @@ export const CallOut = ({ iconType={!loading ? iconType : undefined} className={'mainCallout'} > - {message ?

{message}

: null} + {message}
diff --git a/public/store/DetectorsStore.tsx b/public/store/DetectorsStore.tsx index daca1a244..3f1aad94b 100644 --- a/public/store/DetectorsStore.tsx +++ b/public/store/DetectorsStore.tsx @@ -91,6 +91,12 @@ export class DetectorsStore implements IDetectorsStore { */ private state: IDetectorsState | undefined; + /** + * List of all shown toasts + * @private + */ + private toasts: any[] = []; + constructor( service: DetectorsService, notifications: NotificationsStart, @@ -132,23 +138,31 @@ export class DetectorsStore implements IDetectorsStore { ): void => { if (!type) type = 'primary'; - const btn = btnText ? ( + const closeToasts = () => { + while (this.toasts.length) { + const toast = this.toasts.pop(); + this.notifications?.toasts.remove(toast); + closeToasts(); + } + }; + + const btn = btnText && ( { btnHandler && btnHandler(e); this.hideCallout(); - this.notifications?.toasts.remove(toast); + closeToasts(); }} size="s" > {btnText} - ) : null; + ); const messageBody = ( - {message ?

{message}

: null}
+ {message && {message}} {btn}
); @@ -157,7 +171,7 @@ export class DetectorsStore implements IDetectorsStore { type, title, message: messageBody, - closeHandler: this.showCallout, + closeHandler: this.hideCallout, }); let toastGenerator; @@ -176,11 +190,12 @@ export class DetectorsStore implements IDetectorsStore { break; } - const toast = toastGenerator.bind(this.notifications?.toasts)({ - title: title, - text: this.mountToaster(messageBody), - toastLifeTimeMs: 5000, - }); + this.toasts.push( + toastGenerator.bind(this.notifications?.toasts)({ + title: title, + text: this.mountToaster(messageBody), + }) + ); }; private mountToaster = (component: React.ReactElement) => (container: HTMLElement) => { From dea7ddc94c45e8399a69159a84b4658060a1b50f Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 27 Mar 2023 11:16:47 +0200 Subject: [PATCH 09/64] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- .../containers/Detector/DetectorDetails.tsx | 2 + public/pages/Main/Main.tsx | 16 ++++- public/pages/Main/components/Callout.tsx | 54 +++++++------- public/store/DetectorsStore.tsx | 72 +++++++++---------- 4 files changed, 77 insertions(+), 67 deletions(-) diff --git a/public/pages/Detectors/containers/Detector/DetectorDetails.tsx b/public/pages/Detectors/containers/Detector/DetectorDetails.tsx index e71871703..2b6d88ca8 100644 --- a/public/pages/Detectors/containers/Detector/DetectorDetails.tsx +++ b/public/pages/Detectors/containers/Detector/DetectorDetails.tsx @@ -222,6 +222,8 @@ export class DetectorDetails extends React.Component { }, }; - DataStore.detectors.setCalloutHandler(this.showCallout); + DataStore.detectors.setHandlers(this.showCallout, this.showToast); } showCallout = (callout?: ICalloutProps) => { @@ -102,6 +105,12 @@ export default class Main extends Component { }); }; + showToast = (toasts?: any[]) => { + this.setState({ + toasts, + }); + }; + componentDidMount(): void { this.updateSelectedNavItem(); } @@ -426,6 +435,11 @@ export default class Main extends Component {
+ ) } diff --git a/public/pages/Main/components/Callout.tsx b/public/pages/Main/components/Callout.tsx index 1213dbd0b..3255ca411 100644 --- a/public/pages/Main/components/Callout.tsx +++ b/public/pages/Main/components/Callout.tsx @@ -30,6 +30,33 @@ export interface ICalloutProps { closeHandler?: (callout?: ICalloutProps) => void; } +export const toastTypes: { + [Key in TCalloutColor]: TCalloutIcon; +} = { + primary: 'iInCircle', + success: 'check', + warning: 'help', + danger: 'alert', +}; + +export const resolveType = (type?: ICalloutType | TCalloutColor): ICalloutType => { + if (type === undefined) { + return { + color: 'primary', + iconType: 'iInCircle', + }; + } else { + if (typeof type === 'string') { + return { + color: type, + iconType: toastTypes[type], + }; + } else { + return type; + } + } +}; + export const CallOut = ({ title, message, @@ -38,33 +65,6 @@ export const CallOut = ({ loading = false, closeHandler, }: ICalloutProps) => { - const toastTypes: { - [Key in TCalloutColor]: TCalloutIcon; - } = { - primary: 'iInCircle', - success: 'check', - warning: 'help', - danger: 'alert', - }; - - const resolveType = (type?: ICalloutType | TCalloutColor): ICalloutType => { - if (type === undefined) { - return { - color: 'primary', - iconType: 'iInCircle', - }; - } else { - if (typeof type === 'string') { - return { - color: type, - iconType: toastTypes[type], - }; - } else { - return type; - } - } - }; - const closeCallout = () => closeHandler && closeHandler(undefined); const getTitle = (): JSX.Element => { diff --git a/public/store/DetectorsStore.tsx b/public/store/DetectorsStore.tsx index 3f1aad94b..0ff7e7514 100644 --- a/public/store/DetectorsStore.tsx +++ b/public/store/DetectorsStore.tsx @@ -4,17 +4,18 @@ */ import React from 'react'; -import ReactDOM from 'react-dom'; import { DetectorsService } from '../services'; import { NotificationsStart } from 'opensearch-dashboards/public'; import { CreateDetectorState } from '../pages/CreateDetector/containers/CreateDetector'; -import { ICalloutProps, TCalloutColor } from '../pages/Main/components/Callout'; +import { ICalloutProps, resolveType, TCalloutColor } from '../pages/Main/components/Callout'; import { CreateDetectorResponse, ISavedObjectsService, ServerResponse } from '../../types'; import { CreateMappingsResponse } from '../../server/models/interfaces'; import { logTypesWithDashboards, ROUTES } from '../utils/constants'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { Toast } from '@opensearch-project/oui/src/eui_components/toast/global_toast_list'; import { RouteComponentProps } from 'react-router-dom'; import { DataStore } from './DataStore'; +import { v4 as uuidv4 } from 'uuid'; export interface IDetectorsStore { readonly service: DetectorsService; @@ -29,7 +30,10 @@ export interface IDetectorsStore { dashboardId?: string; ok: boolean; }>; - setCalloutHandler: (calloutHandler: (callout?: ICalloutProps) => void) => void; + setHandlers: ( + calloutHandler: (callout?: ICalloutProps) => void, + toastHandler: (toasts?: Toast[]) => void + ) => void; } export interface IDetectorsCache {} @@ -95,7 +99,7 @@ export class DetectorsStore implements IDetectorsStore { * List of all shown toasts * @private */ - private toasts: any[] = []; + private toasts: Toast[] = []; constructor( service: DetectorsService, @@ -138,12 +142,9 @@ export class DetectorsStore implements IDetectorsStore { ): void => { if (!type) type = 'primary'; - const closeToasts = () => { - while (this.toasts.length) { - const toast = this.toasts.pop(); - this.notifications?.toasts.remove(toast); - closeToasts(); - } + const closeAllToasts = () => { + this.toasts = []; + this.showToast(this.toasts); }; const btn = btnText && ( @@ -152,7 +153,7 @@ export class DetectorsStore implements IDetectorsStore { onClick={(e: any) => { btnHandler && btnHandler(e); this.hideCallout(); - closeToasts(); + closeAllToasts(); }} size="s" > @@ -174,33 +175,15 @@ export class DetectorsStore implements IDetectorsStore { closeHandler: this.hideCallout, }); - let toastGenerator; - switch (type) { - case 'danger': - toastGenerator = this.notifications?.toasts.addDanger; - break; - case 'warning': - toastGenerator = this.notifications?.toasts.addWarning; - break; - case 'success': - toastGenerator = this.notifications?.toasts.addSuccess; - break; - default: - toastGenerator = this.notifications?.toasts.addInfo; - break; - } - - this.toasts.push( - toastGenerator.bind(this.notifications?.toasts)({ - title: title, - text: this.mountToaster(messageBody), - }) - ); - }; - - private mountToaster = (component: React.ReactElement) => (container: HTMLElement) => { - ReactDOM.render(component, container); - return () => ReactDOM.unmountComponentAtNode(container); + const { color, iconType } = resolveType(type); + this.toasts.push({ + title, + color, + iconType, + id: `toastsKey_${uuidv4()}`, + text: messageBody, + }); + this.showToast(this.toasts); }; private viewDetectorConfiguration = (): void => { @@ -329,7 +312,18 @@ export class DetectorsStore implements IDetectorsStore { private hideCallout = (): void => this.showCallout(undefined); - public setCalloutHandler = (calloutHandler: (callout?: ICalloutProps) => void): void => { + private showToast = (toasts?: Toast[] | undefined): void => {}; + + public hideToast = (removedToast: any): void => { + const toasts = this.toasts.filter((toast: Toast) => toast.id !== removedToast.id); + this.showToast(toasts); + }; + + public setHandlers = ( + calloutHandler: (callout?: ICalloutProps) => void, + toastHandler: (toasts?: Toast[]) => void + ): void => { this.showCallout = calloutHandler; + this.showToast = toastHandler; }; } From 7649e8c572e50c4858e08f8def92bdd895167c76 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 27 Mar 2023 11:48:40 +0200 Subject: [PATCH 10/64] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- public/store/DetectorsStore.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/store/DetectorsStore.tsx b/public/store/DetectorsStore.tsx index 0ff7e7514..4f909cb8c 100644 --- a/public/store/DetectorsStore.tsx +++ b/public/store/DetectorsStore.tsx @@ -315,8 +315,8 @@ export class DetectorsStore implements IDetectorsStore { private showToast = (toasts?: Toast[] | undefined): void => {}; public hideToast = (removedToast: any): void => { - const toasts = this.toasts.filter((toast: Toast) => toast.id !== removedToast.id); - this.showToast(toasts); + this.toasts = this.toasts.filter((toast: Toast) => toast.id !== removedToast.id); + this.showToast(this.toasts); }; public setHandlers = ( From fae75fb85c88216d3333d7aae0ce97f12b2d3165 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 28 Mar 2023 12:56:37 +0200 Subject: [PATCH 11/64] [FEATURE] Provide empty states for Findings and Alerts page #471 Signed-off-by: Jovan Cvetkovic --- .../pages/Alerts/containers/Alerts/Alerts.tsx | 37 +++++++- .../Alerts/__snapshots__/Alerts.test.tsx.snap | 94 +++++++++++++------ .../FindingsTable/FindingsTable.tsx | 28 +++++- .../Findings/containers/Findings/Findings.tsx | 21 ++++- .../Overview/containers/Overview/Overview.tsx | 2 + 5 files changed, 145 insertions(+), 37 deletions(-) diff --git a/public/pages/Alerts/containers/Alerts/Alerts.tsx b/public/pages/Alerts/containers/Alerts/Alerts.tsx index 3482e8fc2..4fe4c03e0 100644 --- a/public/pages/Alerts/containers/Alerts/Alerts.tsx +++ b/public/pages/Alerts/containers/Alerts/Alerts.tsx @@ -17,6 +17,7 @@ import { EuiSuperDatePicker, EuiTitle, EuiToolTip, + EuiEmptyPrompt, } from '@elastic/eui'; import { FieldValueSelectionFilterConfigType } from '@elastic/eui/src/components/search_bar/filters/field_value_selection_filter'; import dateMath from '@elastic/datemath'; @@ -81,6 +82,7 @@ export interface AlertsState { loading: boolean; timeUnit: TimeUnit; dateFormat: string; + widgetEmptyMessage: React.ReactNode | undefined; } const groupByOptions = [ @@ -112,6 +114,7 @@ export class Alerts extends Component { detectors: {}, timeUnit: timeUnits.timeUnit, dateFormat: timeUnits.dateFormat, + widgetEmptyMessage: undefined, }; } @@ -148,7 +151,20 @@ export class Alerts extends Component { const filteredAlerts = alerts.filter((alert) => moment(alert.last_notification_time).isBetween(moment(startMoment), moment(endMoment)) ); - this.setState({ alertsFiltered: true, filteredAlerts: filteredAlerts }); + this.setState({ + alertsFiltered: true, + filteredAlerts: filteredAlerts, + widgetEmptyMessage: filteredAlerts.length ? undefined : ( + + No alerts.Adjust the time range to see more + results. +

+ } + /> + ), + }); renderVisualization(this.generateVisualizationSpec(filteredAlerts), 'alerts-view'); }; @@ -413,6 +429,7 @@ export class Alerts extends Component { flyoutData, loading, recentlyUsedRanges, + widgetEmptyMessage, } = this.state; const { @@ -505,7 +522,7 @@ export class Alerts extends Component { />
- + @@ -514,7 +531,20 @@ export class Alerts extends Component { {this.createGroupByControl()} - + {!alerts || alerts.length === 0 ? ( + No alerts} + body={ +

+ Adjust the time range to see more results or create alert triggers in your{' '} + detectors to + generate alerts. +

+ } + /> + ) : ( + + )}
@@ -532,6 +562,7 @@ export class Alerts extends Component { sorting={sorting} selection={selection} loading={loading} + message={widgetEmptyMessage} /> diff --git a/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap b/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap index 050feb771..8175e6547 100644 --- a/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap +++ b/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap @@ -707,10 +707,10 @@ exports[` spec renders the component 1`] = `
@@ -873,42 +873,76 @@ exports[` spec renders the component 1`] = `
- + Adjust the time range to see more results or create alert triggers in your + + + detectors + + to generate alerts. +

+ } + title={ +

+ No alerts +

+ } >
- +

+ No alerts +

+ + - - - - + +
+ + +
+

+ Adjust the time range to see more results or create alert triggers in your + + + + detectors + + + to generate alerts. +

+
+
- -
-
+
- +
diff --git a/public/pages/Findings/components/FindingsTable/FindingsTable.tsx b/public/pages/Findings/components/FindingsTable/FindingsTable.tsx index 53cfd6e53..6b9838d2c 100644 --- a/public/pages/Findings/components/FindingsTable/FindingsTable.tsx +++ b/public/pages/Findings/components/FindingsTable/FindingsTable.tsx @@ -12,6 +12,7 @@ import { EuiInMemoryTable, EuiLink, EuiToolTip, + EuiEmptyPrompt, } from '@elastic/eui'; import { FieldValueSelectionFilterConfigType } from '@elastic/eui/src/components/search_bar/filters/field_value_selection_filter'; import dateMath from '@elastic/datemath'; @@ -48,6 +49,7 @@ interface FindingsTableState { flyout: object | undefined; flyoutOpen: boolean; selectedFinding?: Finding; + widgetEmptyMessage: React.ReactNode | undefined; } export default class FindingsTable extends Component { @@ -59,6 +61,7 @@ export default class FindingsTable extends Component moment(finding.timestamp).isBetween(moment(startMoment), moment(endMoment)) ); - this.setState({ findingsFiltered: true, filteredFindings: filteredFindings }); + this.setState({ + findingsFiltered: true, + filteredFindings: filteredFindings, + widgetEmptyMessage: + filteredFindings.length || findings.length ? undefined : ( + + No findings.Adjust the time range to see + more results. +

+ } + /> + ), + }); this.props.onFindingsFiltered(filteredFindings); }; @@ -142,7 +159,13 @@ export default class FindingsTable extends Component[] = [ { @@ -285,6 +308,7 @@ export default class FindingsTable extends Component {flyoutOpen && flyout}
diff --git a/public/pages/Findings/containers/Findings/Findings.tsx b/public/pages/Findings/containers/Findings/Findings.tsx index b76b654e6..2385cafc1 100644 --- a/public/pages/Findings/containers/Findings/Findings.tsx +++ b/public/pages/Findings/containers/Findings/Findings.tsx @@ -14,6 +14,8 @@ import { EuiSpacer, EuiSuperDatePicker, EuiTitle, + EuiEmptyPrompt, + EuiLink, } from '@elastic/eui'; import FindingsTable from '../../components/FindingsTable'; import FindingsService from '../../../../services/FindingsService'; @@ -37,7 +39,6 @@ import { } from '../../../Overview/utils/helpers'; import { CoreServicesContext } from '../../../../components/core_services'; import { Finding } from '../../models/interfaces'; -import { Detector } from '../../../../../models/interfaces'; import { FeatureChannelList } from '../../../../../server/models/interfaces'; import { getNotificationChannels, @@ -54,6 +55,7 @@ import { NotificationsStart } from 'opensearch-dashboards/public'; import { DateTimeFilter } from '../../../Overview/models/interfaces'; import { ChartContainer } from '../../../../components/Charts/ChartContainer'; import { DataStore } from '../../../../store/DataStore'; +import { Detector } from '../../../../../types'; interface FindingsProps extends RouteComponentProps { detectorService: DetectorsService; @@ -340,7 +342,22 @@ class Findings extends Component { {this.createGroupByControl()} - + {!findings || findings.length === 0 ? ( + No findings} + body={ +

+ Adjust the time range to see more results or{' '} + + create a detector + {' '} + to generate findings. +

+ } + /> + ) : ( + + )}
diff --git a/public/pages/Overview/containers/Overview/Overview.tsx b/public/pages/Overview/containers/Overview/Overview.tsx index 4ba1c5218..64c21ea4d 100644 --- a/public/pages/Overview/containers/Overview/Overview.tsx +++ b/public/pages/Overview/containers/Overview/Overview.tsx @@ -11,6 +11,7 @@ import { EuiPopover, EuiSuperDatePicker, EuiTitle, + EuiSpacer, } from '@elastic/eui'; import React, { useContext, useEffect, useMemo, useState } from 'react'; import { @@ -166,6 +167,7 @@ export const Overview: React.FC = (props) => { /> + Date: Fri, 31 Mar 2023 19:26:38 +0200 Subject: [PATCH 12/64] [FEATURE] Create detector \ Refactor and move field mapping to first the page of create detector feature #495 Signed-off-by: Jovan Cvetkovic --- public/app.scss | 2 +- .../containers/ConfigureFieldMapping.tsx | 257 ++++++++++-------- .../DetectionRules/DetectionRules.tsx | 8 +- .../DetectorDataSource/DetectorDataSource.tsx | 4 +- .../DetectorSchedule/DetectorSchedule.tsx | 15 +- .../components/DetectorSchedule/Interval.tsx | 3 +- .../components/DetectorType/DetectorType.tsx | 18 ++ .../containers/DefineDetector.tsx | 101 ++++--- .../containers/CreateDetector.tsx | 22 +- public/pages/CreateDetector/models/types.ts | 7 +- .../pages/CreateDetector/utils/constants.ts | 8 +- .../AlertTriggerView/AlertTriggerView.tsx | 3 +- .../AlertTriggersView/AlertTriggersView.tsx | 2 +- .../DetectorDetailsView.tsx | 1 - 14 files changed, 247 insertions(+), 204 deletions(-) diff --git a/public/app.scss b/public/app.scss index ffbc4e3a0..987c76e30 100644 --- a/public/app.scss +++ b/public/app.scss @@ -119,4 +119,4 @@ $euiTextColor: $euiColorDarkestShade !default; .sa-overview-widget-empty tbody > .euiTableRow > .euiTableRowCell { border-bottom: none; -} +} diff --git a/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx b/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx index 1be4d27cc..eb09b1e43 100644 --- a/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx +++ b/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx @@ -15,15 +15,14 @@ import { EuiPanel, } from '@elastic/eui'; import FieldMappingsTable from '../components/RequiredFieldMapping'; -import { createDetectorSteps } from '../../../utils/constants'; import { ContentPanel } from '../../../../../components/ContentPanel'; -import { Detector, FieldMapping } from '../../../../../../models/interfaces'; +import { FieldMapping } from '../../../../../../models/interfaces'; import { EMPTY_FIELD_MAPPINGS_VIEW } from '../utils/constants'; -import { DetectorCreationStep } from '../../../models/types'; import { GetFieldMappingViewResponse } from '../../../../../../server/models/interfaces'; import FieldMappingService from '../../../../../services/FieldMappingService'; import { MappingViewType } from '../components/RequiredFieldMapping/FieldMappingsTable'; import { CreateDetectorRulesState } from '../../DefineDetector/components/DetectionRules/DetectionRules'; +import { Detector } from '../../../../../../types'; export interface ruleFieldToIndexFieldMap { [fieldName: string]: string; @@ -36,12 +35,12 @@ interface ConfigureFieldMappingProps extends RouteComponentProps { fieldMappings: FieldMapping[]; loading: boolean; enabledRules: CreateDetectorRulesState['allRules']; - updateDataValidState: (step: DetectorCreationStep, isValid: boolean) => void; replaceFieldMappings: (mappings: FieldMapping[]) => void; } interface ConfigureFieldMappingState { loading: boolean; + detector: Detector; mappingsData: GetFieldMappingViewResponse; createdMappings: ruleFieldToIndexFieldMap; invalidMappingFieldNames: string[]; @@ -62,6 +61,7 @@ export default class ConfigureFieldMapping extends Component< mappingsData: EMPTY_FIELD_MAPPINGS_VIEW, createdMappings, invalidMappingFieldNames: [], + detector: props.detector, }; } @@ -69,10 +69,27 @@ export default class ConfigureFieldMapping extends Component< this.getAllMappings(); }; + componentDidUpdate( + prevProps: Readonly, + prevState: Readonly, + snapshot?: any + ) { + if (prevProps.detector !== this.props.detector) { + this.setState( + { + detector: this.props.detector, + }, + () => { + this.getAllMappings(); + } + ); + } + } + private getRuleFieldsForEnabledRules(): Set { const ruleFieldsForEnabledRules = new Set(); this.props.enabledRules.forEach((rule) => { - rule._source.query_field_names.forEach((fieldname) => { + rule._source.query_field_names.forEach((fieldname: { value: string }) => { ruleFieldsForEnabledRules.add(fieldname.value); }); }); @@ -81,42 +98,44 @@ export default class ConfigureFieldMapping extends Component< } getAllMappings = async () => { - this.setState({ loading: true }); - const mappingsView = await this.props.filedMappingService.getMappingsView( - this.props.detector.inputs[0].detector_input.indices[0], - this.props.detector.detector_type.toLowerCase() - ); - if (mappingsView.ok) { - const existingMappings = { ...this.state.createdMappings }; - const ruleFieldsForEnabledRules = this.getRuleFieldsForEnabledRules(); - const unmappedRuleFields = new Set(mappingsView.response.unmapped_field_aliases); - - Object.keys(mappingsView.response.properties).forEach((ruleFieldName) => { - // Filter the mappings view to include only the rule fields for the enabled rules - if (!ruleFieldsForEnabledRules.has(ruleFieldName)) { - delete mappingsView.response.properties[ruleFieldName]; - return; - } - - existingMappings[ruleFieldName] = - this.state.createdMappings[ruleFieldName] || - mappingsView.response.properties[ruleFieldName].path; - }); - mappingsView.response.unmapped_field_aliases?.forEach((ruleFieldName) => { - if (!ruleFieldsForEnabledRules.has(ruleFieldName)) { - unmappedRuleFields.delete(ruleFieldName); - } - }); - this.setState({ - createdMappings: existingMappings, - mappingsData: { - ...mappingsView.response, - unmapped_field_aliases: Array.from(unmappedRuleFields), - }, - }); - this.updateMappingSharedState(existingMappings); + if (this.state.detector.inputs[0]?.detector_input.indices[0]) { + this.setState({ loading: true }); + const mappingsView = await this.props.filedMappingService.getMappingsView( + this.state.detector.inputs[0].detector_input.indices[0], + this.state.detector.detector_type.toLowerCase() + ); + if (mappingsView.ok) { + const existingMappings = { ...this.state.createdMappings }; + const ruleFieldsForEnabledRules = this.getRuleFieldsForEnabledRules(); + const unmappedRuleFields = new Set(mappingsView.response.unmapped_field_aliases); + + Object.keys(mappingsView.response.properties).forEach((ruleFieldName) => { + // Filter the mappings view to include only the rule fields for the enabled rules + if (!ruleFieldsForEnabledRules.has(ruleFieldName)) { + delete mappingsView.response.properties[ruleFieldName]; + return; + } + + existingMappings[ruleFieldName] = + this.state.createdMappings[ruleFieldName] || + mappingsView.response.properties[ruleFieldName].path; + }); + mappingsView.response.unmapped_field_aliases?.forEach((ruleFieldName) => { + if (!ruleFieldsForEnabledRules.has(ruleFieldName)) { + unmappedRuleFields.delete(ruleFieldName); + } + }); + this.setState({ + createdMappings: existingMappings, + mappingsData: { + ...mappingsView.response, + unmapped_field_aliases: Array.from(unmappedRuleFields), + }, + }); + this.updateMappingSharedState(existingMappings); + } + this.setState({ loading: false }); } - this.setState({ loading: false }); }; /** @@ -153,7 +172,6 @@ export default class ConfigureFieldMapping extends Component< invalidMappingFieldNames: invalidMappingFieldNames, }); this.updateMappingSharedState(newMappings); - this.props.updateDataValidState(DetectorCreationStep.CONFIGURE_FIELD_MAPPING, true); }; updateMappingSharedState = (createdMappings: ruleFieldToIndexFieldMap) => { @@ -197,74 +215,48 @@ export default class ConfigureFieldMapping extends Component< const indexFieldOptions = Array.from(logFields); return ( -
- -

{createDetectorSteps[DetectorCreationStep.CONFIGURE_FIELD_MAPPING].title}

-
- - - To perform threat detection, known field names from your log data source are automatically - mapped to rule field names. Additional fields that may require manual mapping will be - shown below. - - - - - + <> + - -

{`Automatically mapped fields (${mappedRuleFields.length})`}

+ <> + +

Configure field mapping - optional

-
+ + + To perform threat detection, known field names from your log data source are + automatically mapped to rule field names. Additional fields that may require + manual mapping will be shown below. + + } - buttonProps={{ style: { paddingLeft: '10px', paddingRight: '10px' } }} - id={'mappedFieldsAccordion'} + buttonProps={{ style: { padding: '5px' } }} + id={'mappedTitleFieldsAccordion'} initialIsOpen={false} > - - {...this.props} - loading={loading} - ruleFields={mappedRuleFields} - indexFields={indexFieldOptions} - mappingProps={{ - type: MappingViewType.Edit, - existingMappings, - invalidMappingFieldNames, - onMappingCreation: this.onMappingCreation, - }} - /> - - - - - - {unmappedRuleFields.length > 0 ? ( - <> - {pendingCount > 0 ? ( - -

- To generate accurate findings, we recommend mapping the following security rules - fields with the log fields in your data source. -

-
- ) : ( - -

Your data source have been mapped with all security rule fields.

-
- )} - - + + + +
{`Automatically mapped fields (${mappedRuleFields.length})`}
+
+
+ } + buttonProps={{ + style: { paddingLeft: '5px', paddingRight: '5px', paddingTop: '5px' }, + }} + id={'mappedFieldsAccordion'} + initialIsOpen={false} + > + {...this.props} loading={loading} - ruleFields={unmappedRuleFields} + ruleFields={mappedRuleFields} indexFields={indexFieldOptions} mappingProps={{ type: MappingViewType.Edit, @@ -273,23 +265,62 @@ export default class ConfigureFieldMapping extends Component< onMappingCreation: this.onMappingCreation, }} /> - - - - ) : ( - <> - -

- Your data source(s) have been mapped with all security rule fields. No action is - needed. -

-
+ + - - )} - - + + {unmappedRuleFields.length > 0 ? ( + <> + {pendingCount > 0 ? ( + +

+ To generate accurate findings, we recommend mapping the following security + rules fields with the log fields in your data source. +

+
+ ) : ( + +

Your data source have been mapped with all security rule fields.

+
+ )} + + + + + + {...this.props} + loading={loading} + ruleFields={unmappedRuleFields} + indexFields={indexFieldOptions} + mappingProps={{ + type: MappingViewType.Edit, + existingMappings, + invalidMappingFieldNames, + onMappingCreation: this.onMappingCreation, + }} + /> + + + ) : ( + <> + +

+ Your data source(s) have been mapped with all security rule fields. No action + is needed. +

+
+ + )} +
+ + + + + ); } } diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectionRules/DetectionRules.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectionRules/DetectionRules.tsx index 121b2115b..882c8852e 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectionRules/DetectionRules.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectionRules/DetectionRules.tsx @@ -4,7 +4,6 @@ */ import { - EuiPanel, EuiAccordion, EuiTitle, EuiHorizontalRule, @@ -17,7 +16,7 @@ import { DetectionRulesTable } from './DetectionRulesTable'; import { RuleItem, RuleItemInfo } from './types/interfaces'; import { RuleViewerFlyout } from '../../../../../Rules/components/RuleViewerFlyout/RuleViewerFlyout'; import { RuleTableItem } from '../../../../../Rules/utils/helpers'; -import { RuleItemInfoBase } from '../../../../../Rules/models/types'; +import { RuleItemInfoBase } from '../../../../../../../types'; export interface CreateDetectorRulesState { allRules: RuleItemInfo[]; @@ -82,7 +81,7 @@ export const DetectionRules: React.FC = ({ }; return ( - + <> {flyoutData ? ( setFlyoutData(() => null)} @@ -102,7 +101,6 @@ export const DetectionRules: React.FC = ({ } - buttonProps={{ style: { paddingLeft: '10px', paddingRight: '10px' } }} id={'detectorRulesAccordion'} initialIsOpen={false} isLoading={loading} @@ -117,6 +115,6 @@ export const DetectionRules: React.FC = ({ onRuleDetails={onRuleDetails} /> - + ); }; diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx index 1f19fba0a..7aa617ec8 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx @@ -25,7 +25,7 @@ import { FieldMappingService } from '../../../../../../services'; interface DetectorDataSourceProps { detectorIndices: string[]; indexService: IndexService; - filedMappingService: FieldMappingService; + fieldMappingService: FieldMappingService; isEdit: boolean; onDetectorInputIndicesChange: (selectedOptions: EuiComboBoxOptionOption[]) => void; notifications: NotificationsStart; @@ -109,7 +109,7 @@ export default class DetectorDataSource extends Component< for (const indexName of allIndices) { if (!this.indicesMappings[indexName]) { const detectorType = this.props.detector_type.toLowerCase(); - const result = await this.props.filedMappingService.getMappingsView( + const result = await this.props.fieldMappingService.getMappingsView( indexName, detectorType ); diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/DetectorSchedule.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/DetectorSchedule.tsx index e34824e72..2816d0ddb 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/DetectorSchedule.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/DetectorSchedule.tsx @@ -5,14 +5,14 @@ import { ContentPanel } from '../../../../../../components/ContentPanel'; import React from 'react'; -import { EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui'; -import FormFieldHeader from '../../../../../../components/FormFieldHeader'; -import { Detector, PeriodSchedule } from '../../../../../../../models/interfaces'; +import { EuiSelectOption } from '@elastic/eui'; +import { PeriodSchedule } from '../../../../../../../models/interfaces'; import { Interval } from './Interval'; import { CustomCron } from './CustomCron'; import { Daily } from './Daily'; import { Monthly } from './Monthly'; import { Weekly } from './Weekly'; +import { Detector } from '../../../../../../../types'; const frequencies: EuiSelectOption[] = [{ value: 'interval', text: 'By interval' }]; @@ -53,15 +53,6 @@ export class DetectorSchedule extends React.Component< return ( - }> - - - ); diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx index 171b32711..1d1f7e296 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx @@ -13,7 +13,8 @@ import { } from '@elastic/eui'; import FormFieldHeader from '../../../../../../components/FormFieldHeader'; import React from 'react'; -import { Detector, PeriodSchedule } from '../../../../../../../models/interfaces'; +import { PeriodSchedule } from '../../../../../../../models/interfaces'; +import { Detector } from '../../../../../../../types'; export interface IntervalProps { detector: Detector; diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorType/DetectorType.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorType/DetectorType.tsx index 7fb2dc616..aeb6e9fa6 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorType/DetectorType.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorType/DetectorType.tsx @@ -9,10 +9,17 @@ import { EuiFormRow, EuiFlexGrid, EuiFlexItem, EuiRadio, EuiSpacer } from '@elas import { FormFieldHeader } from '../../../../../../components/FormFieldHeader/FormFieldHeader'; import { DETECTOR_TYPES } from '../../../../../Detectors/utils/constants'; import { DetectorTypeOption } from '../../../../../Detectors/models/interfaces'; +import { CreateDetectorRulesState, DetectionRules } from '../DetectionRules/DetectionRules'; +import { RuleItem } from '../DetectionRules/types/interfaces'; interface DetectorTypeProps { detectorType: string; onDetectorTypeChange: (detectorType: string) => void; + rulesState: CreateDetectorRulesState; + loadingRules?: boolean; + onPageChange: (page: { index: number; size: number }) => void; + onRuleToggle: (changedItem: RuleItem, isActive: boolean) => void; + onAllRulesToggle: (enabled: boolean) => void; } interface DetectorTypeState { @@ -67,6 +74,7 @@ export default class DetectorType extends Component )); + return ( {radioButtons} + + + + ); } diff --git a/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx b/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx index 474288563..c8738fe1f 100644 --- a/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx @@ -6,7 +6,7 @@ import React, { Component } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { EuiSpacer, EuiTitle, EuiText, EuiCallOut } from '@elastic/eui'; -import { Detector, PeriodSchedule } from '../../../../../../models/interfaces'; +import { PeriodSchedule } from '../../../../../../models/interfaces'; import DetectorBasicDetailsForm from '../components/DetectorDetails'; import DetectorDataSource from '../components/DetectorDataSource'; import DetectorType from '../components/DetectorType'; @@ -16,19 +16,18 @@ import { MIN_NUM_DATA_SOURCES } from '../../../../Detectors/utils/constants'; import { DetectorCreationStep } from '../../../models/types'; import { DetectorSchedule } from '../components/DetectorSchedule/DetectorSchedule'; import { RuleItem } from '../components/DetectionRules/types/interfaces'; -import { - CreateDetectorRulesState, - DetectionRules, -} from '../components/DetectionRules/DetectionRules'; +import { CreateDetectorRulesState } from '../components/DetectionRules/DetectionRules'; import { NotificationsStart } from 'opensearch-dashboards/public'; -import _ from 'lodash'; import { logTypesWithDashboards } from '../../../../../utils/constants'; +import ConfigureFieldMapping from '../../ConfigureFieldMapping'; +import { Detector, FieldMapping } from '../../../../../../types'; interface DefineDetectorProps extends RouteComponentProps { detector: Detector; isEdit: boolean; indexService: IndexService; - filedMappingService: FieldMappingService; + fieldMappingService: FieldMappingService; + fieldMappings: FieldMapping[]; rulesState: CreateDetectorRulesState; notifications: NotificationsStart; loadingRules?: boolean; @@ -37,11 +36,33 @@ interface DefineDetectorProps extends RouteComponentProps { onPageChange: (page: { index: number; size: number }) => void; onRuleToggle: (changedItem: RuleItem, isActive: boolean) => void; onAllRulesToggle: (enabled: boolean) => void; + replaceFieldMappings: (mappings: FieldMapping[]) => void; } -interface DefineDetectorState {} +interface DefineDetectorState { + detector: Detector; +} export default class DefineDetector extends Component { + constructor(props: DefineDetectorProps) { + super(props); + this.state = { + detector: this.props.detector, + }; + } + + componentDidUpdate( + prevProps: Readonly, + prevState: Readonly, + snapshot?: any + ) { + if (prevProps.detector !== this.props.detector) { + this.setState({ + detector: this.props.detector, + }); + } + } + updateDetectorCreationState(detector: Detector) { const isDataValid = !!detector.name && @@ -55,7 +76,7 @@ export default class DefineDetector extends Component { const newDetector: Detector = { - ...this.props.detector, + ...this.state.detector, name: detectorName, }; @@ -63,9 +84,9 @@ export default class DefineDetector extends Component { - const { inputs } = this.props.detector; + const { inputs } = this.state.detector; const newDetector: Detector = { - ...this.props.detector, + ...this.state.detector, inputs: [ { detector_input: { @@ -82,9 +103,9 @@ export default class DefineDetector extends Component[]) => { const detectorIndices = selectedOptions.map((selectedOption) => selectedOption.label); - const { inputs } = this.props.detector; + const { inputs } = this.state.detector; const newDetector: Detector = { - ...this.props.detector, + ...this.state.detector, inputs: [ { detector_input: { @@ -101,7 +122,7 @@ export default class DefineDetector extends Component { const newDetector: Detector = { - ...this.props.detector, + ...this.state.detector, detector_type: detectorType, }; @@ -109,9 +130,9 @@ export default class DefineDetector extends Component { - const { inputs } = this.props.detector; + const { inputs } = this.state.detector; const newDetector: Detector = { - ...this.props.detector, + ...this.state.detector, inputs: [ { detector_input: { @@ -129,9 +150,9 @@ export default class DefineDetector extends Component { - const { inputs } = this.props.detector; + const { inputs } = this.state.detector; const newDetector: Detector = { - ...this.props.detector, + ...this.state.detector, inputs: [ { detector_input: { @@ -150,7 +171,7 @@ export default class DefineDetector extends Component { const newDetector: Detector = { - ...this.props.detector, + ...this.state.detector, schedule, }; @@ -158,16 +179,9 @@ export default class DefineDetector extends Component @@ -206,16 +220,11 @@ export default class DefineDetector extends Component - - - - @@ -235,6 +244,18 @@ export default class DefineDetector extends Component ) : null} + rule.enabled)} + replaceFieldMappings={this.props.replaceFieldMappings} + /> + + + { + updateDataValidState = (step: DetectorCreationStep | string, isValid: boolean): void => { this.setState({ stepDataValid: { ...this.state.stepDataValid, @@ -289,25 +288,14 @@ export default class CreateDetector extends Component - ); - case DetectorCreationStep.CONFIGURE_FIELD_MAPPING: - return ( - rule.enabled)} replaceFieldMappings={this.replaceFieldMappings} updateDataValidState={this.updateDataValidState} /> diff --git a/public/pages/CreateDetector/models/types.ts b/public/pages/CreateDetector/models/types.ts index f1aff01f5..157f62ae9 100644 --- a/public/pages/CreateDetector/models/types.ts +++ b/public/pages/CreateDetector/models/types.ts @@ -5,9 +5,8 @@ export enum DetectorCreationStep { DEFINE_DETECTOR = 1, - CONFIGURE_FIELD_MAPPING = 2, - CONFIGURE_ALERTS = 3, - REVIEW_CREATE = 4, + CONFIGURE_ALERTS = 2, + REVIEW_CREATE = 3, } export interface DefineDetectorData { @@ -40,7 +39,7 @@ export interface ReviewAndCreateData { export interface DetectorDataByStep { [DetectorCreationStep.DEFINE_DETECTOR]: DefineDetectorData; - [DetectorCreationStep.CONFIGURE_FIELD_MAPPING]: ConfigureFieldMappingData; + ['CONFIGURE_FIELD_MAPPING']: ConfigureFieldMappingData; [DetectorCreationStep.CONFIGURE_ALERTS]: ConfigureAlertsData; [DetectorCreationStep.REVIEW_CREATE]: ReviewAndCreateData; } diff --git a/public/pages/CreateDetector/utils/constants.ts b/public/pages/CreateDetector/utils/constants.ts index e3d09b4c4..91cc66760 100644 --- a/public/pages/CreateDetector/utils/constants.ts +++ b/public/pages/CreateDetector/utils/constants.ts @@ -11,17 +11,13 @@ export const createDetectorSteps: Record {rulesCanFold ? detectorRules : null} - {rulesCanFold ? null : detectorRules} From 9997e3b3ad85188e16359da2ef3cfad230dfc330 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 3 Apr 2023 11:04:08 +0200 Subject: [PATCH 13/64] [FEATURE] Create detector \ Refactor and move field mapping to first the page of create detector feature #495 Signed-off-by: Jovan Cvetkovic --- .../components/ReviewAndCreate/containers/ReviewAndCreate.tsx | 2 +- public/pages/CreateDetector/containers/CreateDetector.tsx | 1 - public/pages/CreateDetector/models/types.ts | 1 - .../components/FieldMappingsView/FieldMappingsView.tsx | 2 +- .../containers/AlertTriggersView/AlertTriggersView.tsx | 2 +- .../containers/DetectorDetailsView/DetectorDetailsView.tsx | 3 +-- 6 files changed, 4 insertions(+), 7 deletions(-) diff --git a/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx b/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx index 7cccd0740..23d730664 100644 --- a/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx +++ b/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx @@ -28,7 +28,7 @@ export class ReviewAndCreate extends React.Component { - this.props.setDetectorCreationStep(DetectorCreationStep.CONFIGURE_FIELD_MAPPING); + this.props.setDetectorCreationStep(DetectorCreationStep.DEFINE_DETECTOR); }; setConfigureAlertsStep = () => { diff --git a/public/pages/CreateDetector/containers/CreateDetector.tsx b/public/pages/CreateDetector/containers/CreateDetector.tsx index 10915720f..fb68813c3 100644 --- a/public/pages/CreateDetector/containers/CreateDetector.tsx +++ b/public/pages/CreateDetector/containers/CreateDetector.tsx @@ -67,7 +67,6 @@ export default class CreateDetector extends Component void; notifications: NotificationsStart; - isEditable: boolean; + isEditable?: boolean; } const columns: EuiBasicTableColumn[] = [ diff --git a/public/pages/Detectors/containers/AlertTriggersView/AlertTriggersView.tsx b/public/pages/Detectors/containers/AlertTriggersView/AlertTriggersView.tsx index 406e566f2..5129f3144 100644 --- a/public/pages/Detectors/containers/AlertTriggersView/AlertTriggersView.tsx +++ b/public/pages/Detectors/containers/AlertTriggersView/AlertTriggersView.tsx @@ -23,7 +23,7 @@ export interface AlertTriggersViewProps { detector: Detector; editAlertTriggers: () => void; notifications: NotificationsStart; - isEditable: boolean; + isEditable?: boolean; } export const AlertTriggersView: React.FC = ({ diff --git a/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx b/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx index e07254e1c..cbafe7f7d 100644 --- a/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx +++ b/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { DetectorBasicDetailsView } from '../../components/DetectorBasicDetailsView/DetectorBasicDetailsView'; import { DetectorRulesView } from '../../components/DetectorRulesView/DetectorRulesView'; -import { EuiSpacer } from '@elastic/eui'; import { RuleItem } from '../../../CreateDetector/components/DefineDetector/components/DetectionRules/types/interfaces'; import { NotificationsStart } from 'opensearch-dashboards/public'; import { Detector } from '../../../../../types'; @@ -20,7 +19,7 @@ export interface DetectorDetailsViewProps { dashboardId?: string; editBasicDetails: () => void; editDetectorRules: (enabledRules: RuleItem[], allRuleItems: RuleItem[]) => void; - isEditable: boolean; + isEditable?: boolean; } export interface DetectorDetailsViewState {} From b2af92309dbb6e3e3aafb9da8cb42db24fac48b6 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 3 Apr 2023 11:15:03 +0200 Subject: [PATCH 14/64] [FEATURE] Create detector \ Refactor and move field mapping to first the page of create detector feature #495 Signed-off-by: Jovan Cvetkovic --- .../components/ReviewAndCreate/containers/ReviewAndCreate.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx b/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx index 23d730664..56906b26a 100644 --- a/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx +++ b/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx @@ -9,9 +9,10 @@ import { DetectorDetailsView } from '../../../../Detectors/containers/DetectorDe import { FieldMappingsView } from '../../../../Detectors/components/FieldMappingsView/FieldMappingsView'; import { AlertTriggersView } from '../../../../Detectors/containers/AlertTriggersView/AlertTriggersView'; import { RouteComponentProps } from 'react-router-dom'; -import { Detector, FieldMapping } from '../../../../../../models/interfaces'; +import { FieldMapping } from '../../../../../../models/interfaces'; import { DetectorCreationStep } from '../../../models/types'; import { NotificationsStart } from 'opensearch-dashboards/public'; +import { Detector } from '../../../../../../types'; export interface ReviewAndCreateProps extends RouteComponentProps { detector: Detector; From a29bb412147c39e69140fbac2838e5eb620c7a7c Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 4 Apr 2023 11:36:15 +0200 Subject: [PATCH 15/64] [FEATURE] Create detector \ Refactor and move field mapping to first the page of create detector feature #495 Signed-off-by: Jovan Cvetkovic --- .../containers/ConfigureFieldMapping.tsx | 80 +++++++++++-------- .../DetectorDataSource/DetectorDataSource.tsx | 53 ++++++------ 2 files changed, 73 insertions(+), 60 deletions(-) diff --git a/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx b/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx index eb09b1e43..dce0ae695 100644 --- a/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx +++ b/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx @@ -15,7 +15,6 @@ import { EuiPanel, } from '@elastic/eui'; import FieldMappingsTable from '../components/RequiredFieldMapping'; -import { ContentPanel } from '../../../../../components/ContentPanel'; import { FieldMapping } from '../../../../../../models/interfaces'; import { EMPTY_FIELD_MAPPINGS_VIEW } from '../utils/constants'; import { GetFieldMappingViewResponse } from '../../../../../../server/models/interfaces'; @@ -44,6 +43,7 @@ interface ConfigureFieldMappingState { mappingsData: GetFieldMappingViewResponse; createdMappings: ruleFieldToIndexFieldMap; invalidMappingFieldNames: string[]; + fieldMappingIsOpen: boolean; } export default class ConfigureFieldMapping extends Component< @@ -62,6 +62,7 @@ export default class ConfigureFieldMapping extends Component< createdMappings, invalidMappingFieldNames: [], detector: props.detector, + fieldMappingIsOpen: false, }; } @@ -131,6 +132,7 @@ export default class ConfigureFieldMapping extends Component< ...mappingsView.response, unmapped_field_aliases: Array.from(unmappedRuleFields), }, + fieldMappingIsOpen: !!unmappedRuleFields.size, }); this.updateMappingSharedState(existingMappings); } @@ -186,7 +188,13 @@ export default class ConfigureFieldMapping extends Component< }; render() { - const { loading, mappingsData, createdMappings, invalidMappingFieldNames } = this.state; + const { + loading, + mappingsData, + createdMappings, + invalidMappingFieldNames, + fieldMappingIsOpen, + } = this.state; const existingMappings: ruleFieldToIndexFieldMap = { ...createdMappings, }; @@ -233,10 +241,14 @@ export default class ConfigureFieldMapping extends Component< } buttonProps={{ style: { padding: '5px' } }} id={'mappedTitleFieldsAccordion'} - initialIsOpen={false} + initialIsOpen={!!unmappedRuleFields.length} + forceState={fieldMappingIsOpen ? 'open' : 'closed'} + onToggle={(isOpen) => { + this.setState({ fieldMappingIsOpen: isOpen }); + }} > - + - - - {...this.props} - loading={loading} - ruleFields={mappedRuleFields} - indexFields={indexFieldOptions} - mappingProps={{ - type: MappingViewType.Edit, - existingMappings, - invalidMappingFieldNames, - onMappingCreation: this.onMappingCreation, - }} - /> + + + {...this.props} + loading={loading} + ruleFields={mappedRuleFields} + indexFields={indexFieldOptions} + mappingProps={{ + type: MappingViewType.Edit, + existingMappings, + invalidMappingFieldNames, + onMappingCreation: this.onMappingCreation, + }} + /> + @@ -274,7 +287,7 @@ export default class ConfigureFieldMapping extends Component< <> {pendingCount > 0 ? (

@@ -284,26 +297,27 @@ export default class ConfigureFieldMapping extends Component< ) : ( -

Your data source have been mapped with all security rule fields.

+

Your data source(s) have been mapped with all security rule fields.

)} - - - {...this.props} - loading={loading} - ruleFields={unmappedRuleFields} - indexFields={indexFieldOptions} - mappingProps={{ - type: MappingViewType.Edit, - existingMappings, - invalidMappingFieldNames, - onMappingCreation: this.onMappingCreation, - }} - /> - + +
Pending field mappings
+
+ + {...this.props} + loading={loading} + ruleFields={unmappedRuleFields} + indexFields={indexFieldOptions} + mappingProps={{ + type: MappingViewType.Edit, + existingMappings, + invalidMappingFieldNames, + onMappingCreation: this.onMappingCreation, + }} + /> ) : ( <> diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx index 7aa617ec8..e992472f2 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx @@ -37,7 +37,7 @@ interface DetectorDataSourceState { fieldTouched: boolean; indexOptions: IndexOption[]; errorMessage?: string; - message: string[]; + differentLogTypeDetected: boolean; } export default class DetectorDataSource extends Component< @@ -52,7 +52,7 @@ export default class DetectorDataSource extends Component< loading: true, fieldTouched: props.isEdit, indexOptions: [], - message: [], + differentLogTypeDetected: false, }; } @@ -120,23 +120,19 @@ export default class DetectorDataSource extends Component< if (!_.isEmpty(this.indicesMappings)) { let firstMapping: string[] = []; let firstMatchMappingIndex: string = ''; - let message: string[] = []; + let differentLogTypeDetected = false; for (let indexName in this.indicesMappings) { if (this.indicesMappings.hasOwnProperty(indexName)) { if (!firstMapping.length) firstMapping = this.indicesMappings[indexName]; !firstMatchMappingIndex.length && (firstMatchMappingIndex = indexName); if (!_.isEqual(firstMapping, this.indicesMappings[indexName])) { - message = [ - `We recommend creating separate detectors for each of the following log sources:`, - firstMatchMappingIndex, - indexName, - ]; + differentLogTypeDetected = true; break; } } } - this.setState({ message }); + this.setState({ differentLogTypeDetected }); } this.props.onDetectorInputIndicesChange(options); @@ -144,28 +140,17 @@ export default class DetectorDataSource extends Component< render() { const { detectorIndices } = this.props; - const { loading, fieldTouched, indexOptions, errorMessage, message } = this.state; + const { + loading, + fieldTouched, + indexOptions, + errorMessage, + differentLogTypeDetected, + } = this.state; const isInvalid = fieldTouched && detectorIndices.length < MIN_NUM_DATA_SOURCES; return ( - {message.length ? ( - <> - - {message.map((messageItem: string, index: number) => ( - - {index === 0 ? '' : 'ㅤ•ㅤ'} - {messageItem} -
-
- ))} -
- - - ) : null} @@ -186,6 +171,20 @@ export default class DetectorDataSource extends Component< data-test-subj={'define-detector-select-data-source'} /> + {differentLogTypeDetected ? ( + <> + + + + To avoid issues with field mappings, we recommend creating separate detectors for + different log types. + + + + ) : null}
); } From 01ebf2d2827f9046d37be5e8fa515cf9e883f387 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 4 Apr 2023 12:05:01 +0200 Subject: [PATCH 16/64] [FEATURE] Create detector \ Refactor and move field mapping to first the page of create detector feature #495 Signed-off-by: Jovan Cvetkovic --- .../DetectorDataSource/DetectorDataSource.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx index e992472f2..50da2d817 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx @@ -37,7 +37,7 @@ interface DetectorDataSourceState { fieldTouched: boolean; indexOptions: IndexOption[]; errorMessage?: string; - differentLogTypeDetected: boolean; + differentLogTypesDetected: boolean; } export default class DetectorDataSource extends Component< @@ -52,7 +52,7 @@ export default class DetectorDataSource extends Component< loading: true, fieldTouched: props.isEdit, indexOptions: [], - differentLogTypeDetected: false, + differentLogTypesDetected: false, }; } @@ -120,19 +120,19 @@ export default class DetectorDataSource extends Component< if (!_.isEmpty(this.indicesMappings)) { let firstMapping: string[] = []; let firstMatchMappingIndex: string = ''; - let differentLogTypeDetected = false; + let differentLogTypesDetected = false; for (let indexName in this.indicesMappings) { if (this.indicesMappings.hasOwnProperty(indexName)) { if (!firstMapping.length) firstMapping = this.indicesMappings[indexName]; !firstMatchMappingIndex.length && (firstMatchMappingIndex = indexName); if (!_.isEqual(firstMapping, this.indicesMappings[indexName])) { - differentLogTypeDetected = true; + differentLogTypesDetected = true; break; } } } - this.setState({ differentLogTypeDetected }); + this.setState({ differentLogTypesDetected }); } this.props.onDetectorInputIndicesChange(options); @@ -145,7 +145,7 @@ export default class DetectorDataSource extends Component< fieldTouched, indexOptions, errorMessage, - differentLogTypeDetected, + differentLogTypesDetected, } = this.state; const isInvalid = fieldTouched && detectorIndices.length < MIN_NUM_DATA_SOURCES; return ( @@ -171,7 +171,7 @@ export default class DetectorDataSource extends Component< data-test-subj={'define-detector-select-data-source'} /> - {differentLogTypeDetected ? ( + {differentLogTypesDetected ? ( <> Date: Tue, 4 Apr 2023 12:06:06 +0200 Subject: [PATCH 17/64] [FEATURE] Create detector \ Refactor and move field mapping to first the page of create detector feature #495 Signed-off-by: Jovan Cvetkovic --- .../UpdateDetectorBasicDetails.test.tsx.snap | 141 ------------------ .../DetectorDetails.test.tsx.snap | 7 - .../DetectorDetailsView.test.tsx.snap | 7 - 3 files changed, 155 deletions(-) diff --git a/public/pages/Detectors/components/UpdateBasicDetails/__snapshots__/UpdateDetectorBasicDetails.test.tsx.snap b/public/pages/Detectors/components/UpdateBasicDetails/__snapshots__/UpdateDetectorBasicDetails.test.tsx.snap index a787a5377..362671c54 100644 --- a/public/pages/Detectors/components/UpdateBasicDetails/__snapshots__/UpdateDetectorBasicDetails.test.tsx.snap +++ b/public/pages/Detectors/components/UpdateBasicDetails/__snapshots__/UpdateDetectorBasicDetails.test.tsx.snap @@ -1170,147 +1170,6 @@ exports[` spec renders the component 1`] = ` } } > - - } - labelType="label" - > -
-
- - - -
-
- - -
-
- - - - -
- - - - - -
-
-
-
-
-
-
-
-
spec renders the component 1`] = ` - -
- spec renders the component 1`] = ` - -
- Date: Wed, 5 Apr 2023 09:11:29 +0200 Subject: [PATCH 18/64] [FEATURE] Create detector \ Refactor and move field mapping to first the page of create detector feature #495 Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 21 +++++++------------ .../DetectorDataSource/DetectorDataSource.tsx | 6 +++--- .../UpdateBasicDetails/UpdateBasicDetails.tsx | 2 +- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 092006e45..ddb3c853a 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -86,7 +86,7 @@ describe('Detectors', () => { cy.get('.euiCallOut') .should('be.visible') - .contains('The selected log sources contain different types of logs'); + .contains('The selected log sources contain different log types'); }); it('...can be created', () => { @@ -140,12 +140,6 @@ describe('Detectors', () => { }); }); - // Click Next button to continue - cy.get('button').contains('Next').click({ force: true }); - - // Check that correct page now showing - cy.contains('Configure field mapping'); - // Select appropriate names to map fields to for (let field_name in testMappings.properties) { const mappedTo = testMappings.properties[field_name].path; @@ -155,8 +149,8 @@ describe('Detectors', () => { }); } - // Continue to next page - cy.get('button').contains('Next').click({ force: true, timeout: 2000 }); + // Click Next button to continue + cy.get('button').contains('Next').click({ force: true }); // Check that correct page now showing cy.contains('Set up alerts'); @@ -245,11 +239,10 @@ describe('Detectors', () => { .realType('Edited description'); // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .ospClear() - .focus() - .realType(cypressIndexWindows); + cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( + `${cypressIndexWindows}{enter}` + ); // Change detector scheduling cy.get(`[data-test-subj="detector-schedule-number-select"]`).ospClear().focus().realType('10'); diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx index 50da2d817..8c6539e37 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx @@ -25,7 +25,7 @@ import { FieldMappingService } from '../../../../../../services'; interface DetectorDataSourceProps { detectorIndices: string[]; indexService: IndexService; - fieldMappingService: FieldMappingService; + fieldMappingService?: FieldMappingService; isEdit: boolean; onDetectorInputIndicesChange: (selectedOptions: EuiComboBoxOptionOption[]) => void; notifications: NotificationsStart; @@ -109,11 +109,11 @@ export default class DetectorDataSource extends Component< for (const indexName of allIndices) { if (!this.indicesMappings[indexName]) { const detectorType = this.props.detector_type.toLowerCase(); - const result = await this.props.fieldMappingService.getMappingsView( + const result = await this.props.fieldMappingService?.getMappingsView( indexName, detectorType ); - result.ok && (this.indicesMappings[indexName] = result.response.unmapped_field_aliases); + result?.ok && (this.indicesMappings[indexName] = result.response.unmapped_field_aliases); } } diff --git a/public/pages/Detectors/components/UpdateBasicDetails/UpdateBasicDetails.tsx b/public/pages/Detectors/components/UpdateBasicDetails/UpdateBasicDetails.tsx index 6ff215498..e97d55734 100644 --- a/public/pages/Detectors/components/UpdateBasicDetails/UpdateBasicDetails.tsx +++ b/public/pages/Detectors/components/UpdateBasicDetails/UpdateBasicDetails.tsx @@ -222,7 +222,7 @@ export const UpdateDetectorBasicDetails: React.FC From f933e28d7ea9098fa6bb6a3bf8e362e83c1afa20 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Wed, 5 Apr 2023 20:07:58 +0200 Subject: [PATCH 19/64] [FEATURE] Create detector \ Refactor and move field mapping to first the page of create detector feature #495 Signed-off-by: Jovan Cvetkovic --- .../AlertCondition/AlertConditionPanel.tsx | 2 +- .../containers/ConfigureAlerts.tsx | 23 ++++++++----------- .../pages/CreateDetector/utils/constants.ts | 2 +- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx index ae7c5ef45..79939763b 100644 --- a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx +++ b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx @@ -311,7 +311,7 @@ export default class AlertConditionPanel extends Component< error={getNameErrorMessage(name, nameIsInvalid, nameFieldTouched)} >

{isEdit - ? 'Edit alert triggers' - : createDetectorSteps[DetectorCreationStep.CONFIGURE_ALERTS].title + - ` (${triggers.length})`} + ? `Alert triggers (${triggers.length})` + : createDetectorSteps[DetectorCreationStep.CONFIGURE_ALERTS].title}

@@ -162,19 +161,15 @@ export default class ConfigureAlerts extends Component -

{alertCondition.name}

+

{isEdit ? alertCondition.name : 'Alert trigger'}

} paddingSize={'none'} initialIsOpen={true} extraAction={ - triggers.length > 1 ? ( - this.onDelete(index)}> - Remove alert trigger - - ) : ( - <> - ) + this.onDelete(index)}> + Remove + } > @@ -197,7 +192,7 @@ export default class ConfigureAlerts extends Component = MAX_ALERT_CONDITIONS} onClick={this.addCondition}> - {`Add ${triggers.length > 0 ? 'another' : 'an'} alert condition`} + {triggers.length > 0 ? 'Add another alert trigger' : 'Add alert triggers'}
); diff --git a/public/pages/CreateDetector/utils/constants.ts b/public/pages/CreateDetector/utils/constants.ts index e3d09b4c4..e06e5599a 100644 --- a/public/pages/CreateDetector/utils/constants.ts +++ b/public/pages/CreateDetector/utils/constants.ts @@ -16,7 +16,7 @@ export const createDetectorSteps: Record Date: Thu, 6 Apr 2023 14:54:40 +0200 Subject: [PATCH 20/64] [FEATURE] Create global state object for async requests #493 Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 77 +++++-------------- .../FieldNameSelector.tsx | 2 +- 2 files changed, 22 insertions(+), 57 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 102e79bee..a1459b5ca 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -53,20 +53,6 @@ const createDetector = (detectorName, dataSource, expectFailure) => { cy.contains('table tr', 'DNS', { timeout: 120000, }); - - // find search, type USB - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - - // Disable all rules - cy.contains('tr', cypressDNSRule, { timeout: 1000 }); - cy.get('table th').within(() => { - cy.get('button').first().click({ force: true }); - }); - - // Enable single rule - cy.contains('table tr', cypressDNSRule).within(() => { - cy.get('button').eq(1).click({ force: true, timeout: 2000 }); - }); }); // Click Next button to continue @@ -303,7 +289,7 @@ describe('Detectors', () => { }); // Confirm number of rules before edit - cy.contains('Active rules (1)'); + cy.contains('Active rules (13)'); // Click "Edit" button in Detector rules panel cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); @@ -327,7 +313,7 @@ describe('Detectors', () => { cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ force: true }); // Confirm 1 rule has been removed from detector - cy.contains('Active rules (0)'); + cy.contains('Active rules (12)'); // Click "Edit" button in Detector rules panel cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); @@ -353,7 +339,7 @@ describe('Detectors', () => { }); // Confirm 1 rule has been added to detector - cy.contains('Active rules (1)'); + cy.contains('Active rules (13)'); }); it('...should update field mappings if data source is changed', () => { @@ -374,25 +360,22 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .ospClear() - .focus() - .realType(cypressIndexWindows) - .realPress('Enter'); + cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( + `${cypressIndexWindows}{enter}` + ); cy.get('.reviewFieldMappings').should('be.visible'); cy.get('.reviewFieldMappings').within(($el) => { cy.get($el).contains('Automatically mapped fields (0)'); + cy.get($el).contains('4 rule fields may need manual mapping'); }); // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .ospClear() - .focus() - .realType(cypressIndexDns) - .realPress('Enter'); + cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( + `${cypressIndexDns}{enter}` + ); cy.get('.reviewFieldMappings').should('be.visible'); cy.get('.reviewFieldMappings').within(($el) => { @@ -420,16 +403,10 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); - // Search for specific rule - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - cy.intercept('mappings/view').as('getMappingsView'); - // Toggle single search result to unchecked - cy.contains('table tr', cypressDNSRule).within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); + cy.get('table th').within(() => { + cy.get('button').first().click({ force: true }); }); cy.wait('@getMappingsView'); @@ -438,42 +415,30 @@ describe('Detectors', () => { cy.get($el).contains('Automatically mapped fields (0)'); }); - //Suspicious DNS Query with B64 Encoded String - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - cy.contains('table tr', cypressDNSRule).within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - cy.wait('@getMappingsView'); - cy.get(`input[placeholder="Search..."]`).ospSearch( - 'Suspicious DNS Query with B64 Encoded String' - ); - cy.contains('table tr', 'Suspicious DNS Query with B64 Encoded String').within(() => { + cy.get(`input[placeholder="Search..."]`).ospSearch('High TXT Records Requests Rate'); + cy.contains('table tr', 'High TXT Records Requests Rate').within(() => { // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. cy.wait(1000); cy.get('button').eq(1).click(); }); - cy.wait('@getMappingsView'); - cy.get('.reviewFieldMappings').should('be.visible'); cy.get('.reviewFieldMappings').within(($el) => { cy.get($el).contains('Automatically mapped fields (1)'); }); - cy.get(`input[placeholder="Search..."]`).ospSearch('High TXT Records Requests Rate'); - cy.contains('table tr', 'High TXT Records Requests Rate').within(() => { + cy.get(`input[placeholder="Search..."]`).ospSearch( + 'Suspicious DNS Query with B64 Encoded String' + ); + + cy.contains('table tr', 'Suspicious DNS Query with B64 Encoded String').within(() => { // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. cy.wait(1000); cy.get('button').eq(1).click(); }); - cy.wait('@getMappingsView'); cy.get('.reviewFieldMappings').should('be.visible'); cy.get('.reviewFieldMappings').within(($el) => { cy.get($el).contains('Automatically mapped fields (1)'); - cy.get($el).contains('1 rule fields may need manual mapping'); }); }); diff --git a/public/pages/CreateDetector/components/ConfigureFieldMapping/components/RequiredFieldMapping/FieldNameSelector.tsx b/public/pages/CreateDetector/components/ConfigureFieldMapping/components/RequiredFieldMapping/FieldNameSelector.tsx index 3abb78b74..c2c7fb722 100644 --- a/public/pages/CreateDetector/components/ConfigureFieldMapping/components/RequiredFieldMapping/FieldNameSelector.tsx +++ b/public/pages/CreateDetector/components/ConfigureFieldMapping/components/RequiredFieldMapping/FieldNameSelector.tsx @@ -39,7 +39,7 @@ export default class FieldNameSelector extends Component Date: Thu, 6 Apr 2023 15:02:09 +0200 Subject: [PATCH 21/64] [FEATURE] Create global state object for async requests #493 Signed-off-by: Jovan Cvetkovic --- .../components/ConfigureAlerts/containers/ConfigureAlerts.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx b/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx index 4dfbfec19..f38785d00 100644 --- a/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx +++ b/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx @@ -77,8 +77,6 @@ export default class ConfigureAlerts extends Component { this.updateBreadcrumbs(); const { - isEdit, - detector, detector: { triggers }, } = this.props; this.getNotificationChannels(); From e3afea6844e62c966dc9caf5f0f3a2ad3053d66e Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Thu, 6 Apr 2023 20:09:40 +0200 Subject: [PATCH 22/64] [FEATURE] Create detector \ Refactor alert triggers per mocks #498 Signed-off-by: Jovan Cvetkovic --- .../containers/ConfigureAlerts.tsx | 74 +++++++++++++++---- .../containers/ReviewAndCreate.tsx | 3 +- .../containers/CreateDetector.tsx | 3 +- .../AlertTriggerView/AlertTriggerView.tsx | 3 +- .../FieldMappingsView/FieldMappingsView.tsx | 2 +- .../AlertTriggersView/AlertTriggersView.tsx | 27 ++++++- .../DetectorDetailsView.tsx | 2 +- 7 files changed, 91 insertions(+), 23 deletions(-) diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx b/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx index f38785d00..ec13deea9 100644 --- a/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx +++ b/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx @@ -13,8 +13,10 @@ import { EuiSpacer, EuiTitle, EuiText, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; -import { createDetectorSteps } from '../../../utils/constants'; import { MAX_ALERT_CONDITIONS } from '../utils/constants'; import AlertConditionPanel from '../components/AlertCondition'; import { DetectorCreationStep } from '../../../models/types'; @@ -39,6 +41,7 @@ interface ConfigureAlertsProps extends RouteComponentProps { updateDataValidState: (step: DetectorCreationStep, isValid: boolean) => void; notificationsService: NotificationsService; hasNotificationPlugin: boolean; + skipAndConfigureHandler: () => void; } interface ConfigureAlertsState { @@ -112,9 +115,11 @@ export default class ConfigureAlerts extends Component { - const isTriggerDataValid = newDetector.triggers.every((trigger) => { - return !!trigger.name && validateName(trigger.name) && trigger.severity; - }); + const isTriggerDataValid = + !newDetector.triggers.length || + newDetector.triggers.every((trigger) => { + return !!trigger.name && validateName(trigger.name) && trigger.severity; + }); this.props.changeDetector(newDetector); this.props.updateDataValidState(DetectorCreationStep.CONFIGURE_ALERTS, isTriggerDataValid); @@ -133,21 +138,45 @@ export default class ConfigureAlerts extends Component { + let title = ( + + + +

Set up alert triggers

+
+ + Get notified when specific rule conditions are found by the detector. + +
+ {triggers?.length && ( + + { + const { changeDetector, detector } = this.props; + changeDetector({ ...detector, triggers: [] }); + skipAndConfigureHandler(); + }} + > + Skip and configure later + + + )} +
+ ); + if (isEdit) { + title = <>Alert triggers (${triggers.length}); + } + + return title; + }; const { loading, notificationChannels } = this.state; return (
- -

- {isEdit - ? `Alert triggers (${triggers.length})` - : createDetectorSteps[DetectorCreationStep.CONFIGURE_ALERTS].title} -

-
- - - Get notified when specific rule conditions are found by the detector. - + {getPageTitle()} @@ -186,6 +215,21 @@ export default class ConfigureAlerts extends Component
))} + {!triggers?.length && ( + +

+ We recommend creating alert triggers to get notified when specific conditions are + found by the detector. +

+

You can also configure alert triggers after the detector is created.

+ + } + /> + )} diff --git a/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx b/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx index 7cccd0740..c0f26638e 100644 --- a/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx +++ b/public/pages/CreateDetector/components/ReviewAndCreate/containers/ReviewAndCreate.tsx @@ -9,9 +9,10 @@ import { DetectorDetailsView } from '../../../../Detectors/containers/DetectorDe import { FieldMappingsView } from '../../../../Detectors/components/FieldMappingsView/FieldMappingsView'; import { AlertTriggersView } from '../../../../Detectors/containers/AlertTriggersView/AlertTriggersView'; import { RouteComponentProps } from 'react-router-dom'; -import { Detector, FieldMapping } from '../../../../../../models/interfaces'; +import { FieldMapping } from '../../../../../../models/interfaces'; import { DetectorCreationStep } from '../../../models/types'; import { NotificationsStart } from 'opensearch-dashboards/public'; +import { Detector } from '../../../../../../types'; export interface ReviewAndCreateProps extends RouteComponentProps { detector: Detector; diff --git a/public/pages/CreateDetector/containers/CreateDetector.tsx b/public/pages/CreateDetector/containers/CreateDetector.tsx index ee00219ae..3e8ff86ea 100644 --- a/public/pages/CreateDetector/containers/CreateDetector.tsx +++ b/public/pages/CreateDetector/containers/CreateDetector.tsx @@ -322,6 +322,7 @@ export default class CreateDetector extends Component ); case DetectorCreationStep.REVIEW_CREATE: @@ -390,7 +391,7 @@ export default class CreateDetector extends Component - Create + Create detector )} diff --git a/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx b/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx index a9fa0bace..97d4ace6c 100644 --- a/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx +++ b/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx @@ -11,12 +11,13 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import { AlertCondition, Detector } from '../../../../../models/interfaces'; +import { AlertCondition } from '../../../../../models/interfaces'; import React from 'react'; import { createTextDetailsGroup } from '../../../../utils/helpers'; import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers'; import { DEFAULT_EMPTY_DATA, getNotificationDetailsHref } from '../../../../utils/constants'; import { FeatureChannelList, RuleInfo } from '../../../../../server/models/interfaces'; +import { Detector } from '../../../../../types'; export interface AlertTriggerViewProps { alertTrigger: AlertCondition; diff --git a/public/pages/Detectors/components/FieldMappingsView/FieldMappingsView.tsx b/public/pages/Detectors/components/FieldMappingsView/FieldMappingsView.tsx index 6dff33d84..76b4d55f9 100644 --- a/public/pages/Detectors/components/FieldMappingsView/FieldMappingsView.tsx +++ b/public/pages/Detectors/components/FieldMappingsView/FieldMappingsView.tsx @@ -18,7 +18,7 @@ export interface FieldMappingsViewProps { existingMappings?: FieldMapping[]; editFieldMappings: () => void; notifications: NotificationsStart; - isEditable: boolean; + isEditable?: boolean; } const columns: EuiBasicTableColumn[] = [ diff --git a/public/pages/Detectors/containers/AlertTriggersView/AlertTriggersView.tsx b/public/pages/Detectors/containers/AlertTriggersView/AlertTriggersView.tsx index 1322eb1b9..af8ab6506 100644 --- a/public/pages/Detectors/containers/AlertTriggersView/AlertTriggersView.tsx +++ b/public/pages/Detectors/containers/AlertTriggersView/AlertTriggersView.tsx @@ -5,9 +5,8 @@ import { ContentPanel } from '../../../../components/ContentPanel'; import React, { useMemo, useEffect, useState, useContext } from 'react'; -import { EuiButton } from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; import { AlertTriggerView } from '../../components/AlertTriggerView/AlertTriggerView'; -import { Detector } from '../../../../../models/interfaces'; import { ServicesContext } from '../../../../services'; import { ServerResponse } from '../../../../../server/models/types'; import { @@ -18,12 +17,13 @@ import { import { errorNotificationToast } from '../../../../utils/helpers'; import { NotificationsStart } from 'opensearch-dashboards/public'; import { DataStore } from '../../../../store/DataStore'; +import { Detector } from '../../../../../types'; export interface AlertTriggersViewProps { detector: Detector; editAlertTriggers: () => void; notifications: NotificationsStart; - isEditable: boolean; + isEditable?: boolean; } export const AlertTriggersView: React.FC = ({ @@ -102,6 +102,27 @@ export const AlertTriggersView: React.FC = ({ rules={rules} /> ))} + {!detector?.triggers?.length && ( + <> + +

No alert triggers defined.

+ + + +

+ We recommend creating alert triggers to get notified when specific conditions are + found by the detector. +

+

You can also configure alert triggers after the detector is created.

+ + } + /> + + )} ); }; diff --git a/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx b/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx index 58d8dcde2..86bb59d91 100644 --- a/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx +++ b/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx @@ -20,7 +20,7 @@ export interface DetectorDetailsViewProps { dashboardId?: string; editBasicDetails: () => void; editDetectorRules: (enabledRules: RuleItem[], allRuleItems: RuleItem[]) => void; - isEditable: boolean; + isEditable?: boolean; } export interface DetectorDetailsViewState {} From 43209b55bd348f84d27d3b2a0cd9483f208a8b0b Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Fri, 7 Apr 2023 10:22:12 +0200 Subject: [PATCH 23/64] [FEATURE] Create global state object for async requests #493 Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 2 +- .../containers/ConfigureFieldMapping.tsx | 3 ++- .../containers/DefineDetector.tsx | 3 ++- .../containers/CreateDetector.tsx | 8 +++--- .../containers/Detector/DetectorDetails.tsx | 18 ++++++------- public/store/DetectorsStore.test.ts | 12 ++++----- public/store/DetectorsStore.tsx | 25 +++++++++++++------ 7 files changed, 41 insertions(+), 30 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index a1459b5ca..b2ddaef9a 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -412,7 +412,7 @@ describe('Detectors', () => { cy.wait('@getMappingsView'); cy.get('.reviewFieldMappings').should('be.visible'); cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (0)'); + cy.get($el).contains('Automatically mapped fields (1)'); }); cy.get(`input[placeholder="Search..."]`).ospSearch('High TXT Records Requests Rate'); diff --git a/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx b/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx index 2194d6dc8..d079719f0 100644 --- a/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx +++ b/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx @@ -17,13 +17,14 @@ import { import FieldMappingsTable from '../components/RequiredFieldMapping'; import { createDetectorSteps } from '../../../utils/constants'; import { ContentPanel } from '../../../../../components/ContentPanel'; -import { Detector, FieldMapping } from '../../../../../../models/interfaces'; +import { FieldMapping } from '../../../../../../models/interfaces'; import { EMPTY_FIELD_MAPPINGS_VIEW } from '../utils/constants'; import { DetectorCreationStep } from '../../../models/types'; import { GetFieldMappingViewResponse } from '../../../../../../server/models/interfaces'; import FieldMappingService from '../../../../../services/FieldMappingService'; import { MappingViewType } from '../components/RequiredFieldMapping/FieldMappingsTable'; import { CreateDetectorRulesState } from '../../DefineDetector/components/DetectionRules/DetectionRules'; +import { Detector } from '../../../../../../types'; export interface ruleFieldToIndexFieldMap { [fieldName: string]: string; diff --git a/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx b/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx index a42326c40..ea07a5e83 100644 --- a/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx @@ -6,7 +6,7 @@ import React, { Component } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { EuiSpacer, EuiTitle, EuiText, EuiCallOut } from '@elastic/eui'; -import { Detector, PeriodSchedule } from '../../../../../../models/interfaces'; +import { PeriodSchedule } from '../../../../../../models/interfaces'; import DetectorBasicDetailsForm from '../components/DetectorDetails'; import DetectorDataSource from '../components/DetectorDataSource'; import DetectorType from '../components/DetectorType'; @@ -23,6 +23,7 @@ import { import { NotificationsStart } from 'opensearch-dashboards/public'; import _ from 'lodash'; import { logTypesWithDashboards } from '../../../../../utils/constants'; +import { Detector } from '../../../../../../types'; interface DefineDetectorProps extends RouteComponentProps { detector: Detector; diff --git a/public/pages/CreateDetector/containers/CreateDetector.tsx b/public/pages/CreateDetector/containers/CreateDetector.tsx index 898cc9b78..2dc2a20fa 100644 --- a/public/pages/CreateDetector/containers/CreateDetector.tsx +++ b/public/pages/CreateDetector/containers/CreateDetector.tsx @@ -58,9 +58,9 @@ export default class CreateDetector extends Component { - const pendingState = DataStore.detectors.getState(); - const detector = pendingState?.detectorState?.detector; - const pendingRequests = pendingState?.pendingRequests; + const state = DataStore.detectors.getState(); + const detector = state?.detectorInput?.detector; + const pendingRequests = state?.pendingRequests; this.getTabs(); if (pendingRequests && detector) { @@ -205,7 +205,7 @@ export class DetectorDetails extends React.Component { diff --git a/public/store/DetectorsStore.test.ts b/public/store/DetectorsStore.test.ts index dc3896e41..af98aa191 100644 --- a/public/store/DetectorsStore.test.ts +++ b/public/store/DetectorsStore.test.ts @@ -35,7 +35,7 @@ describe('Detectors store specs', () => { DataStore.detectors.setState( { pendingRequests: [Promise.resolve()], - detectorState: { + detectorInput: { detector: { detector_type: 'test_detector_type' } as typeof DetectorMock, } as CreateDetectorState, }, @@ -43,7 +43,7 @@ describe('Detectors store specs', () => { ); let state = DataStore.detectors.getState(); - expect(state?.detectorState?.detector.detector_type).toBe('test_detector_type'); + expect(state?.detectorInput?.detector.detector_type).toBe('test_detector_type'); DataStore.detectors.deleteState(); state = DataStore.detectors.getState(); @@ -74,13 +74,13 @@ describe('Detectors store specs', () => { }, }), ], - detectorState: { + detectorInput: { detector: { detector_type: 'test_detector_type' } as typeof DetectorMock, } as CreateDetectorState, }, browserHistoryMock ); - const pending = await DataStore.detectors.getPendingState(); + const pending = await DataStore.detectors.resolvePendingCreationRequest(); expect(pending.ok).toBe(true); }); @@ -92,13 +92,13 @@ describe('Detectors store specs', () => { ok: false, }), ], - detectorState: { + detectorInput: { detector: { detector_type: 'test_detector_type' } as typeof DetectorMock, } as CreateDetectorState, }, browserHistoryMock ); - const pending = await DataStore.detectors.getPendingState(); + const pending = await DataStore.detectors.resolvePendingCreationRequest(); expect(pending.ok).toBe(false); }); }); diff --git a/public/store/DetectorsStore.tsx b/public/store/DetectorsStore.tsx index 4f909cb8c..b3a7e63e1 100644 --- a/public/store/DetectorsStore.tsx +++ b/public/store/DetectorsStore.tsx @@ -25,7 +25,7 @@ export interface IDetectorsStore { setState: (state: IDetectorsState, history: RouteComponentProps['history']) => void; getState: () => IDetectorsState | undefined; deleteState: () => void; - getPendingState: () => Promise<{ + resolvePendingCreationRequest: () => Promise<{ detectorId?: string; dashboardId?: string; ok: boolean; @@ -40,13 +40,13 @@ export interface IDetectorsCache {} export interface IDetectorsState { pendingRequests: Promise[]; - detectorState: CreateDetectorState; + detectorInput: CreateDetectorState; } /** * Class is used to make detector's API calls and cache the detectors. * If there is a cache data requests are skipped and result is returned from the cache. - * If cache is invalidated then the request is made to get a new set of data. + * If cache is invalidated then the request is triggered to get a new set of data. * * @class DetectorsStore * @implements IDetectorsStore @@ -187,16 +187,17 @@ export class DetectorsStore implements IDetectorsStore { }; private viewDetectorConfiguration = (): void => { - const pendingState = DataStore.detectors.getState(); - const detectorState = pendingState?.detectorState; + const state = DataStore.detectors.getState(); + const detectorInput = { ...state?.detectorInput }; + DataStore.detectors.deleteState(); + this.history?.push({ pathname: `${ROUTES.DETECTORS_CREATE}`, - state: { detectorState }, + state: { detectorInput }, }); - DataStore.detectors.deleteState(); }; - public getPendingState = async (): Promise<{ + public resolvePendingCreationRequest = async (): Promise<{ detectorId?: string; dashboardId?: string; ok: boolean; @@ -308,10 +309,18 @@ export class DetectorsStore implements IDetectorsStore { }); }; + /** + * A handler function that store gets from the Main component to show/hide the callout message + * @param {ICalloutProps | undefined} callout + */ private showCallout = (callout?: ICalloutProps | undefined): void => {}; private hideCallout = (): void => this.showCallout(undefined); + /** + * A handler function that store gets from the Main component to show/hide the toast message + * @param {Toast[] | undefined} toasts + */ private showToast = (toasts?: Toast[] | undefined): void => {}; public hideToast = (removedToast: any): void => { From 9612f854ee3e2b912fd49d7f262820a929afb2fe Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Fri, 7 Apr 2023 11:29:03 +0200 Subject: [PATCH 24/64] [FEATURE] Create global state object for async requests #493 Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 49 ------------------------- 1 file changed, 49 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index b2ddaef9a..c75fa626d 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -364,26 +364,6 @@ describe('Detectors', () => { cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexWindows}{enter}` ); - - cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (0)'); - cy.get($el).contains('4 rule fields may need manual mapping'); - }); - - // Change input source - cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - `${cypressIndexDns}{enter}` - ); - - cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (1)'); - }); - - // Save changes to detector details - cy.get(`[data-test-subj="save-basic-details-edits"]`).click({ force: true }); }); it('...should update field mappings if rule selection is changed', () => { @@ -411,35 +391,6 @@ describe('Detectors', () => { cy.wait('@getMappingsView'); cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (1)'); - }); - - cy.get(`input[placeholder="Search..."]`).ospSearch('High TXT Records Requests Rate'); - cy.contains('table tr', 'High TXT Records Requests Rate').within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (1)'); - }); - - cy.get(`input[placeholder="Search..."]`).ospSearch( - 'Suspicious DNS Query with B64 Encoded String' - ); - - cy.contains('table tr', 'Suspicious DNS Query with B64 Encoded String').within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (1)'); - }); }); it('...can be deleted', () => { From 2260cfcff266fd6cf11afc4ffab111fdbc926096 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Fri, 7 Apr 2023 13:26:03 +0200 Subject: [PATCH 25/64] [FEATURE] Create detector \ Refactor alert triggers per mocks #498 Signed-off-by: Jovan Cvetkovic --- public/pages/CreateDetector/containers/CreateDetector.tsx | 2 +- .../components/AlertTriggerView/AlertTriggerView.tsx | 7 +++---- .../components/FieldMappingsView/FieldMappingsView.tsx | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/public/pages/CreateDetector/containers/CreateDetector.tsx b/public/pages/CreateDetector/containers/CreateDetector.tsx index 3e8ff86ea..dfbce9aea 100644 --- a/public/pages/CreateDetector/containers/CreateDetector.tsx +++ b/public/pages/CreateDetector/containers/CreateDetector.tsx @@ -366,7 +366,7 @@ export default class CreateDetector extends Component DetectorCreationStep.DEFINE_DETECTOR && ( - Previous + Back )} diff --git a/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx b/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx index 97d4ace6c..a3db9f2dc 100644 --- a/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx +++ b/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx @@ -44,15 +44,15 @@ export const AlertTriggerView: React.FC = ({ const conditionRuleNames = ids.map((ruleId) => rules[ruleId]?._source.title); return (
- {orderPosition > 0 && } - + {orderPosition > 0 && } + {orderPosition === 0 && } -

{`Alert on ${name}`}

+

{name}

} > @@ -104,7 +104,6 @@ export const AlertTriggerView: React.FC = ({ }, ])}
-
); }; diff --git a/public/pages/Detectors/components/FieldMappingsView/FieldMappingsView.tsx b/public/pages/Detectors/components/FieldMappingsView/FieldMappingsView.tsx index 76b4d55f9..da8599dfd 100644 --- a/public/pages/Detectors/components/FieldMappingsView/FieldMappingsView.tsx +++ b/public/pages/Detectors/components/FieldMappingsView/FieldMappingsView.tsx @@ -29,7 +29,7 @@ const columns: EuiBasicTableColumn[] = [ }, { field: 'logFieldName', - name: 'Mapped index field name', + name: 'Mapped log field name', }, ]; From e6bec6af467763d14c8a5f14bcaaf67973dac8a2 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Fri, 7 Apr 2023 13:47:03 +0200 Subject: [PATCH 26/64] [FEATURE] Create detector \ Refactor alert triggers per mocks #498 Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 66 +------------------ .../AlertConditionPanel.test.tsx.snap | 4 +- .../AlertTriggerView.test.tsx.snap | 20 ++---- .../FieldMappingsView.test.tsx.snap | 16 ++--- .../AlertTriggersView.test.tsx.snap | 39 +++-------- 5 files changed, 27 insertions(+), 118 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index edd34bd8a..1dd4c7b64 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -159,10 +159,10 @@ describe('Detectors', () => { cy.get('button').contains('Next').click({ force: true, timeout: 2000 }); // Check that correct page now showing - cy.contains('Set up alerts'); + cy.contains('Set up alert triggers'); // Type name of new trigger - cy.get(`input[placeholder="Enter a name for the alert condition."]`) + cy.get(`input[placeholder="Enter a name to describe the alert condition"]`) .focus() .realType('test_trigger'); @@ -198,7 +198,7 @@ describe('Detectors', () => { cy.contains(detectorName); cy.contains('dns'); cy.contains(cypressIndexDns); - cy.contains('Alert on test_trigger'); + cy.contains('test_trigger'); // Create the detector cy.get('button').contains('Create').click({ force: true }); @@ -362,25 +362,6 @@ describe('Detectors', () => { .realPress('Enter'); cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (0)'); - }); - - // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .ospClear() - .focus() - .realType(cypressIndexDns) - .realPress('Enter'); - - cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (1)'); - }); - - // Save changes to detector details - cy.get(`[data-test-subj="save-basic-details-edits"]`).click({ force: true }); }); it('...should update field mappings if rule selection is changed', () => { @@ -414,47 +395,6 @@ describe('Detectors', () => { cy.wait('@getMappingsView'); cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (0)'); - }); - - //Suspicious DNS Query with B64 Encoded String - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - cy.contains('table tr', cypressDNSRule).within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - cy.wait('@getMappingsView'); - cy.get(`input[placeholder="Search..."]`).ospSearch( - 'Suspicious DNS Query with B64 Encoded String' - ); - cy.contains('table tr', 'Suspicious DNS Query with B64 Encoded String').within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - cy.wait('@getMappingsView'); - cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (1)'); - }); - - cy.get(`input[placeholder="Search..."]`).ospSearch('High TXT Records Requests Rate'); - cy.contains('table tr', 'High TXT Records Requests Rate').within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - cy.wait('@getMappingsView'); - cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (1)'); - cy.get($el).contains('1 rule fields may need manual mapping'); - }); }); it('...can be deleted', () => { diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap index 11e571e9b..c2b3c34f5 100644 --- a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap +++ b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap @@ -40,7 +40,7 @@ Object { class="euiFieldText" data-test-subj="alert-condition-name-0" id="some_html_id" - placeholder="Enter a name for the alert condition." + placeholder="Enter a name to describe the alert condition" type="text" value="alert_name" /> @@ -779,7 +779,7 @@ Object { class="euiFieldText" data-test-subj="alert-condition-name-0" id="some_html_id" - placeholder="Enter a name for the alert condition." + placeholder="Enter a name to describe the alert condition" type="text" value="alert_name" /> diff --git a/public/pages/Detectors/components/AlertTriggerView/__snapshots__/AlertTriggerView.test.tsx.snap b/public/pages/Detectors/components/AlertTriggerView/__snapshots__/AlertTriggerView.test.tsx.snap index cc6d33387..bfceeddf3 100644 --- a/public/pages/Detectors/components/AlertTriggerView/__snapshots__/AlertTriggerView.test.tsx.snap +++ b/public/pages/Detectors/components/AlertTriggerView/__snapshots__/AlertTriggerView.test.tsx.snap @@ -7,10 +7,7 @@ Object {

-

- Alert on alert_name + alert_name

@@ -362,19 +359,13 @@ Object {
-
, "container":

-

- Alert on alert_name + alert_name

@@ -726,9 +717,6 @@ Object {
-
, "debug": [Function], diff --git a/public/pages/Detectors/components/FieldMappingsView/__snapshots__/FieldMappingsView.test.tsx.snap b/public/pages/Detectors/components/FieldMappingsView/__snapshots__/FieldMappingsView.test.tsx.snap index 199178e02..86c71d206 100644 --- a/public/pages/Detectors/components/FieldMappingsView/__snapshots__/FieldMappingsView.test.tsx.snap +++ b/public/pages/Detectors/components/FieldMappingsView/__snapshots__/FieldMappingsView.test.tsx.snap @@ -112,9 +112,9 @@ Object { > - Mapped index field name + Mapped log field name @@ -148,7 +148,7 @@ Object {
- Mapped index field name + Mapped log field name
- Mapped index field name + Mapped log field name
- Mapped index field name + Mapped log field name @@ -353,7 +353,7 @@ Object {
- Mapped index field name + Mapped log field name
- Mapped index field name + Mapped log field name
spec renders the component 1`] = ` >
spec renders the component 1`] = ` size="m" >

- Alert on alert_name + alert_name

} @@ -634,7 +634,7 @@ exports[` spec renders the component 1`] = ` className="euiText euiText--medium" >

- Alert on alert_name + alert_name

@@ -1354,13 +1354,6 @@ exports[` spec renders the component 1`] = `
- -
-
spec renders the component 1`] = ` rules={Object {}} >
- +
- -
- spec renders the component 1`] = ` size="m" >

- Alert on alert_name + alert_name

} @@ -1667,7 +1655,7 @@ exports[` spec renders the component 1`] = ` className="euiText euiText--medium" >

- Alert on alert_name + alert_name

@@ -2387,13 +2375,6 @@ exports[` spec renders the component 1`] = `
- -
-
From 45eacc0a4bc4b3d5aacb9eca417187a913d07299 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Fri, 7 Apr 2023 14:31:48 +0200 Subject: [PATCH 27/64] [FEATURE] Update detector details component #502 Signed-off-by: Jovan Cvetkovic --- .../DetectorBasicDetailsView.tsx | 55 ++++++++++++------- .../DetectorRulesView/DetectorRulesView.tsx | 2 +- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx b/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx index a8eb24151..345ef1360 100644 --- a/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx +++ b/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx @@ -37,23 +37,13 @@ export const DetectorBasicDetailsView: React.FC = const lastUpdated = last_update_time ? moment(last_update_time).format('YYYY-MM-DDTHH:mm') : undefined; - const firstTextDetailsGroupEntries = [ - { label: 'Detector name', content: name }, - { label: 'Log type', content: detector_type.toLowerCase() }, - { label: 'Data source', content: inputs[0].detector_input.indices[0] }, - { - label: 'Detector dashboard', - content: (dashboardId ? ( - window.open(`dashboards#/view/${dashboardId}`, '_blank')}> - {`${name} summary`} - - - ) : ( - 'Not available for this log type' - )) as any, - }, - ]; - + const totalSelected = detector.inputs.reduce((sum, inputObj) => { + return ( + sum + + inputObj.detector_input.custom_rules.length + + inputObj.detector_input.pre_packaged_rules.length + ); + }, 0); return ( = } > - {createTextDetailsGroup(firstTextDetailsGroupEntries, 4)} {createTextDetailsGroup( [ - { label: 'Description', content: inputs[0].detector_input.description }, + { label: 'Detector name', content: name }, + { + label: 'Description', + content: inputs[0].detector_input.description || DEFAULT_EMPTY_DATA, + }, { label: 'Detector schedule', content: detectorSchedule }, + ], + 4 + )} + {createTextDetailsGroup( + [ + { label: 'Data source', content: inputs[0].detector_input.indices.join(', ') }, + { label: 'Log type', content: detector_type.toLowerCase() }, + { + label: 'Detector dashboard', + content: (dashboardId ? ( + window.open(`dashboards#/view/${dashboardId}`, '_blank')}> + {`${name} summary`} + + + ) : ( + 'Not available for this log type' + )) as any, + }, + ], + 4 + )} + {createTextDetailsGroup( + [ + { label: 'Detection rules', content: totalSelected }, { label: 'Created at', content: createdAt || DEFAULT_EMPTY_DATA }, { label: 'Last updated time', content: lastUpdated || DEFAULT_EMPTY_DATA }, ], diff --git a/public/pages/Detectors/components/DetectorRulesView/DetectorRulesView.tsx b/public/pages/Detectors/components/DetectorRulesView/DetectorRulesView.tsx index a8ec85ac6..43be000d6 100644 --- a/public/pages/Detectors/components/DetectorRulesView/DetectorRulesView.tsx +++ b/public/pages/Detectors/components/DetectorRulesView/DetectorRulesView.tsx @@ -110,7 +110,7 @@ export const DetectorRulesView: React.FC = (props) => { }); }, [services, props.detector]); - const getDetectionRulesTitle = () => `View detection rules (${totalSelected})`; + const getDetectionRulesTitle = () => `View detection rules`; const onShowRuleDetails = (rule: RuleTableItem) => { setFlyoutData(() => rule); From 5b0e4ec68774c15f64709ccad4993a117cddc611 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Fri, 7 Apr 2023 14:32:49 +0200 Subject: [PATCH 28/64] [FEATURE] Update detector details component #502 Signed-off-by: Jovan Cvetkovic --- .../DetectorBasicDetailsView.test.tsx.snap | 168 ++++++++++++---- .../DetectorDetails.test.tsx.snap | 183 ++++++++++++++---- .../DetectorDetailsView.test.tsx.snap | 183 ++++++++++++++---- 3 files changed, 420 insertions(+), 114 deletions(-) diff --git a/public/pages/Detectors/components/DetectorBasicDetailsView/__snapshots__/DetectorBasicDetailsView.test.tsx.snap b/public/pages/Detectors/components/DetectorBasicDetailsView/__snapshots__/DetectorBasicDetailsView.test.tsx.snap index 84a8d02af..244da1aca 100644 --- a/public/pages/Detectors/components/DetectorBasicDetailsView/__snapshots__/DetectorBasicDetailsView.test.tsx.snap +++ b/public/pages/Detectors/components/DetectorBasicDetailsView/__snapshots__/DetectorBasicDetailsView.test.tsx.snap @@ -122,7 +122,7 @@ Object {
- Log type + Description
@@ -132,14 +132,60 @@ Object { >
- detector_type + detectorDescription
+
+
+
+ +
+
+
+ Every 1 minute +
+
+
+
+ +
+
- Detector dashboard + Log type
@@ -210,21 +256,14 @@ Object { >
- Not available for this log type + detector_type
- -
-
- Description + Detector dashboard
@@ -256,14 +295,21 @@ Object { >
- detectorDescription + Not available for this log type
+ +
+
- Detector schedule + Detection rules
@@ -295,10 +341,10 @@ Object { >
- Every 1 minute + 2
@@ -507,7 +553,7 @@ Object {
- Log type + Description
@@ -517,14 +563,60 @@ Object { >
- detector_type + detectorDescription
+
+
+
+ +
+
+
+ Every 1 minute +
+
+
+
+ +
+
- Detector dashboard + Log type
@@ -595,21 +687,14 @@ Object { >
- Not available for this log type + detector_type
- -
-
- Description + Detector dashboard
@@ -641,14 +726,21 @@ Object { >
- detectorDescription + Not available for this log type
+ +
+
- Detector schedule + Detection rules
@@ -680,10 +772,10 @@ Object { >
- Every 1 minute + 2
diff --git a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap index 040edd2f2..02cc30fc8 100644 --- a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap +++ b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap @@ -1609,7 +1609,7 @@ exports[` spec renders the component 1`] = ` - Log type + Description } labelType="label" @@ -1644,7 +1644,7 @@ exports[` spec renders the component 1`] = `
- Log type + Description
@@ -1656,19 +1656,19 @@ exports[` spec renders the component 1`] = ` className="euiFormRow__fieldWrapper" >
- detector_type + detectorDescription
@@ -1684,6 +1684,113 @@ exports[` spec renders the component 1`] = ` "minWidth": "25%", } } + > +
+ + Detector schedule + + } + labelType="label" + > +
+
+ + + +
+
+ +
+ Every 1 minute +
+
+
+
+
+
+ + + + +
+ + +
+
spec renders the component 1`] = ` spec renders the component 1`] = ` - Detector dashboard + Log type } labelType="label" @@ -1832,7 +1939,7 @@ exports[` spec renders the component 1`] = `
- Detector dashboard + Log type
@@ -1844,19 +1951,19 @@ exports[` spec renders the component 1`] = ` className="euiFormRow__fieldWrapper" >
- Not available for this log type + detector_type
@@ -1864,22 +1971,9 @@ exports[` spec renders the component 1`] = `
- - - -
- - -
spec renders the component 1`] = ` - Description + Detector dashboard } labelType="label" @@ -1939,7 +2033,7 @@ exports[` spec renders the component 1`] = `
- Description + Detector dashboard
@@ -1951,19 +2045,19 @@ exports[` spec renders the component 1`] = ` className="euiFormRow__fieldWrapper" >
- detectorDescription + Not available for this log type
@@ -1971,9 +2065,22 @@ exports[` spec renders the component 1`] = ` + + + +
+ + +
spec renders the component 1`] = ` - Detector schedule + Detection rules } labelType="label" @@ -2033,7 +2140,7 @@ exports[` spec renders the component 1`] = `
- Detector schedule + Detection rules
@@ -2045,19 +2152,19 @@ exports[` spec renders the component 1`] = ` className="euiFormRow__fieldWrapper" >
- Every 1 minute + 2
@@ -2067,7 +2174,7 @@ exports[` spec renders the component 1`] = ` spec renders the component 1`] = ` spec renders the component 1`] = ` - Log type + Description } labelType="label" @@ -702,7 +702,7 @@ exports[` spec renders the component 1`] = `
- Log type + Description
@@ -714,19 +714,19 @@ exports[` spec renders the component 1`] = ` className="euiFormRow__fieldWrapper" >
- detector_type + detectorDescription
@@ -742,6 +742,113 @@ exports[` spec renders the component 1`] = ` "minWidth": "25%", } } + > +
+ + Detector schedule + + } + labelType="label" + > +
+
+ + + +
+
+ +
+ Every 1 minute +
+
+
+
+
+
+
+ + + +
+ + +
+
spec renders the component 1`] = ` spec renders the component 1`] = ` - Detector dashboard + Log type } labelType="label" @@ -890,7 +997,7 @@ exports[` spec renders the component 1`] = `
- Detector dashboard + Log type
@@ -902,19 +1009,19 @@ exports[` spec renders the component 1`] = ` className="euiFormRow__fieldWrapper" >
- Not available for this log type + detector_type
@@ -922,22 +1029,9 @@ exports[` spec renders the component 1`] = `
- - - -
- - -
spec renders the component 1`] = ` - Description + Detector dashboard } labelType="label" @@ -997,7 +1091,7 @@ exports[` spec renders the component 1`] = `
- Description + Detector dashboard
@@ -1009,19 +1103,19 @@ exports[` spec renders the component 1`] = ` className="euiFormRow__fieldWrapper" >
- detectorDescription + Not available for this log type
@@ -1029,9 +1123,22 @@ exports[` spec renders the component 1`] = ` + + + +
+ + +
spec renders the component 1`] = ` - Detector schedule + Detection rules } labelType="label" @@ -1091,7 +1198,7 @@ exports[` spec renders the component 1`] = `
- Detector schedule + Detection rules
@@ -1103,19 +1210,19 @@ exports[` spec renders the component 1`] = ` className="euiFormRow__fieldWrapper" >
- Every 1 minute + 2
@@ -1125,7 +1232,7 @@ exports[` spec renders the component 1`] = ` spec renders the component 1`] = ` Date: Fri, 7 Apr 2023 14:53:08 +0200 Subject: [PATCH 29/64] [FEATURE] Update detector details component #502 Signed-off-by: Jovan Cvetkovic --- .../DetectorBasicDetailsView.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx b/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx index 345ef1360..4b7c67518 100644 --- a/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx +++ b/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiButton, EuiSpacer, EuiLink, EuiIcon } from '@elastic/eui'; +import { EuiButton, EuiSpacer, EuiLink, EuiIcon, EuiText } from '@elastic/eui'; import React from 'react'; import { ContentPanel } from '../../../../components/ContentPanel'; import { createTextDetailsGroup, parseSchedule } from '../../../../utils/helpers'; @@ -71,7 +71,16 @@ export const DetectorBasicDetailsView: React.FC = )} {createTextDetailsGroup( [ - { label: 'Data source', content: inputs[0].detector_input.indices.join(', ') }, + { + label: 'Data source', + content: ( + <> + {inputs[0].detector_input.indices.map((ind: string) => ( + {ind} + ))} + + ), + }, { label: 'Log type', content: detector_type.toLowerCase() }, { label: 'Detector dashboard', From 0abac6b40511456bafff87eab09315bd2d1f54f0 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Fri, 7 Apr 2023 15:02:15 +0200 Subject: [PATCH 30/64] [FEATURE] Update detector details component #502 Signed-off-by: Jovan Cvetkovic --- .../DetectorBasicDetailsView.test.tsx.snap | 12 ++++++++++-- .../__snapshots__/DetectorDetails.test.tsx.snap | 8 +++++++- .../__snapshots__/DetectorDetailsView.test.tsx.snap | 8 +++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/public/pages/Detectors/components/DetectorBasicDetailsView/__snapshots__/DetectorBasicDetailsView.test.tsx.snap b/public/pages/Detectors/components/DetectorBasicDetailsView/__snapshots__/DetectorBasicDetailsView.test.tsx.snap index 244da1aca..2d2001232 100644 --- a/public/pages/Detectors/components/DetectorBasicDetailsView/__snapshots__/DetectorBasicDetailsView.test.tsx.snap +++ b/public/pages/Detectors/components/DetectorBasicDetailsView/__snapshots__/DetectorBasicDetailsView.test.tsx.snap @@ -220,7 +220,11 @@ Object { data-test-subj="text-details-group-content-data-source" id="some_html_id" > - .windows +
+ .windows +
@@ -651,7 +655,11 @@ Object { data-test-subj="text-details-group-content-data-source" id="some_html_id" > - .windows +
+ .windows +
diff --git a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap index 02cc30fc8..2bd46f07f 100644 --- a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap +++ b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap @@ -1869,7 +1869,13 @@ exports[` spec renders the component 1`] = ` onBlur={[Function]} onFocus={[Function]} > - .windows + +
+ .windows +
+
diff --git a/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap b/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap index b6b34ca3d..50617fb8e 100644 --- a/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap +++ b/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap @@ -927,7 +927,13 @@ exports[` spec renders the component 1`] = ` onBlur={[Function]} onFocus={[Function]} > - .windows + +
+ .windows +
+
From a7632b1cd481e78c89b6b0281b009c8df170041a Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Fri, 7 Apr 2023 16:25:43 +0200 Subject: [PATCH 31/64] Feature] update detector details component #504 Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 60 ------------------------- 1 file changed, 60 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index edd34bd8a..7368bfbb6 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -362,25 +362,6 @@ describe('Detectors', () => { .realPress('Enter'); cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (0)'); - }); - - // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .ospClear() - .focus() - .realType(cypressIndexDns) - .realPress('Enter'); - - cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (1)'); - }); - - // Save changes to detector details - cy.get(`[data-test-subj="save-basic-details-edits"]`).click({ force: true }); }); it('...should update field mappings if rule selection is changed', () => { @@ -414,47 +395,6 @@ describe('Detectors', () => { cy.wait('@getMappingsView'); cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (0)'); - }); - - //Suspicious DNS Query with B64 Encoded String - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - cy.contains('table tr', cypressDNSRule).within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - cy.wait('@getMappingsView'); - cy.get(`input[placeholder="Search..."]`).ospSearch( - 'Suspicious DNS Query with B64 Encoded String' - ); - cy.contains('table tr', 'Suspicious DNS Query with B64 Encoded String').within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - cy.wait('@getMappingsView'); - cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (1)'); - }); - - cy.get(`input[placeholder="Search..."]`).ospSearch('High TXT Records Requests Rate'); - cy.contains('table tr', 'High TXT Records Requests Rate').within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - cy.wait('@getMappingsView'); - cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.reviewFieldMappings').within(($el) => { - cy.get($el).contains('Automatically mapped fields (1)'); - cy.get($el).contains('1 rule fields may need manual mapping'); - }); }); it('...can be deleted', () => { From 2c019b77cc528ccc3eedad005203c39badaac501 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 10 Apr 2023 20:02:45 +0200 Subject: [PATCH 32/64] Feature] update detector details component #504 Signed-off-by: Jovan Cvetkovic --- ... create_dns_rule_with_name_selection.json} | 2 +- .../create_dns_rule_with_type_selection.json | 26 + .../fixtures/sample_dns_index_settings.json | 21 + ...son => sample_windows_index_settings.json} | 3 - cypress/integration/1_detectors.spec.js | 478 ++++++++++-------- cypress/integration/3_alerts.spec.js | 2 +- cypress/integration/4_findings.spec.js | 2 +- cypress/integration/5_integrations.spec.js | 4 +- 8 files changed, 310 insertions(+), 228 deletions(-) rename cypress/fixtures/integration_tests/rule/{create_dns_rule.json => create_dns_rule_with_name_selection.json} (79%) create mode 100644 cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json create mode 100644 cypress/fixtures/sample_dns_index_settings.json rename cypress/fixtures/{sample_index_settings.json => sample_windows_index_settings.json} (89%) diff --git a/cypress/fixtures/integration_tests/rule/create_dns_rule.json b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json similarity index 79% rename from cypress/fixtures/integration_tests/rule/create_dns_rule.json rename to cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json index 5e38ab4bd..ae89e69da 100644 --- a/cypress/fixtures/integration_tests/rule/create_dns_rule.json +++ b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json @@ -16,7 +16,7 @@ } ], "log_source": "", - "detection": "selection:\n query:\n - QWE\n - ASD\n - YXC\ncondition: selection", + "detection": "selection:\n DnsQuestionName:\n - QWE\n - ASD\n - YXC\ncondition: selection", "level": "low", "false_positives": [ { diff --git a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json new file mode 100644 index 000000000..508a09b74 --- /dev/null +++ b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json @@ -0,0 +1,26 @@ +{ + "id": "25b9c01c-350d-4b95-bed1-836d04a4f325", + "category": "dns", + "title": "Cypress DNS Type Rule", + "description": "Detects DNS type as QWE", + "status": "experimental", + "author": "Cypress Tests", + "references": [ + { + "value": "" + } + ], + "tags": [ + { + "value": "dns.low" + } + ], + "log_source": "", + "detection": "selection:\n DnsAnswerType:\n - QWE\n - ASD\n - YXC\ncondition: selection", + "level": "low", + "false_positives": [ + { + "value": "" + } + ] +} diff --git a/cypress/fixtures/sample_dns_index_settings.json b/cypress/fixtures/sample_dns_index_settings.json new file mode 100644 index 000000000..3ccc2b613 --- /dev/null +++ b/cypress/fixtures/sample_dns_index_settings.json @@ -0,0 +1,21 @@ +{ + "mappings": { + "properties": { + "DnsQuestionName": { + "type": "text" + }, + "DnsAnswerType": { + "type": "text" + }, + "DnsQuestionRegisteredDomain": { + "type": "text" + } + } + }, + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "1" + } + } +} diff --git a/cypress/fixtures/sample_index_settings.json b/cypress/fixtures/sample_windows_index_settings.json similarity index 89% rename from cypress/fixtures/sample_index_settings.json rename to cypress/fixtures/sample_windows_index_settings.json index a8a5294a7..f794e671e 100644 --- a/cypress/fixtures/sample_index_settings.json +++ b/cypress/fixtures/sample_windows_index_settings.json @@ -18,9 +18,6 @@ }, "ServiceName": { "type": "text" - }, - "DnsQuestionName": { - "type": "text" } } }, diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index fab464866..8d63c151f 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -4,8 +4,14 @@ */ import { OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; -import sample_index_settings from '../fixtures/sample_index_settings.json'; -import dns_rule_data from '../fixtures/integration_tests/rule/create_dns_rule.json'; +import sample_windows_index_settings from '../fixtures/sample_windows_index_settings.json'; +import sample_dns_index_settings from '../fixtures/sample_dns_index_settings.json'; +import dns_name_rule_data from '../fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json'; +import dns_type_rule_data from '../fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json'; + +const cypressIndexDns = 'cypress-index-dns'; +const cypressIndexWindows = 'cypress-index-windows'; +const detectorName = 'test detector'; const testMappings = { properties: { @@ -16,7 +22,12 @@ const testMappings = { }, }; -const cypressDNSRule = dns_rule_data.title; +const cypressDNSRule = dns_name_rule_data.title; +const cypressDNSTypeRule = dns_type_rule_data.title; + +const getNameField = () => cy.get(`input[placeholder="Enter a name for the detector."]`); +const getDataSourceField = () => cy.get(`.euiComboBox__input input`); +const getNextButton = () => cy.get('.euiButton').filter(':contains("Next")'); const createDetector = (detectorName, dataSource, expectFailure) => { // Locate Create detector button click to start @@ -55,12 +66,8 @@ const createDetector = (detectorName, dataSource, expectFailure) => { }); }); - // Click Next button to continue - cy.get('button').contains('Next').click({ force: true }); - // Check that correct page now showing - cy.contains('Configure field mapping'); - + cy.contains('Configure field mapping - optional'); if (!expectFailure) { // Select appropriate names to map fields to for (let field_name in testMappings.properties) { @@ -116,7 +123,6 @@ const createDetector = (detectorName, dataSource, expectFailure) => { cy.contains('Detector details'); cy.contains(detectorName); cy.contains('dns'); - cy.contains(cypressIndexDns); cy.contains('test_trigger'); // Create the detector @@ -144,17 +150,13 @@ const createDetector = (detectorName, dataSource, expectFailure) => { }; describe('Detectors', () => { - const cypressIndexDns = 'cypress-index-dns'; - const cypressIndexWindows = 'cypress-index-windows'; - const detectorName = 'test detector'; - before(() => { cy.cleanUpTests(); - cy.createIndex(cypressIndexWindows, sample_index_settings); + cy.createIndex(cypressIndexWindows, sample_windows_index_settings); // Create test index - cy.createIndex(cypressIndexDns, sample_index_settings).then(() => + cy.createIndex(cypressIndexDns, sample_dns_index_settings).then(() => cy .request('POST', '_plugins/_security_analytics/rules/_search?prePackaged=true', { from: 0, @@ -169,7 +171,8 @@ describe('Detectors', () => { .should('have.property', 'status', 200) ); - cy.createRule(dns_rule_data); + cy.createRule(dns_name_rule_data); + cy.createRule(dns_type_rule_data); }); beforeEach(() => { @@ -184,196 +187,231 @@ describe('Detectors', () => { }); }); - it('...should show mappings warning', () => { - // Locate Create detector button click to start - cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true }); - - // Check to ensure process started - cy.waitForPageLoad('create-detector', { - contains: 'Define detector', - }); - - // Select our pre-seeded data source (check cypressIndexDns) - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .focus() - .realType(cypressIndexDns); - - // Select threat detector type (Windows logs) - cy.get(`input[id="dns"]`).click({ force: true }); - - // Select our pre-seeded data source (check cypressIndexDns) - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .focus() - .realType(cypressIndexWindows) - .realPress('Enter'); - - cy.get('.euiCallOut') - .should('be.visible') - .contains('The selected log sources contain different types of logs'); - }); + // it('...should validate form', () => { + // // Locate Create detector button click to start + // cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true }); + // + // // Check to ensure process started + // cy.waitForPageLoad('create-detector', { + // contains: 'Define detector', + // }); + // + // getNextButton().should('be.disabled'); + // + // getNameField().should('be.empty'); + // getNameField().type('text').blur(); + // + // cy.get('.euiFormErrorText').contains( + // 'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.' + // ); + // + // getNameField().type(' and more text').blur(); + // cy.get('.euiFormErrorText').should('not.exist'); + // getNextButton().should('be.disabled'); + // + // getDataSourceField().focus().blur(); + // cy.get('.euiFormErrorText').contains('Select an input source'); + // getNextButton().should('be.disabled'); + // + // getDataSourceField().type(`${cypressIndexDns}{enter}`); + // cy.get('.euiFormErrorText').should('not.exist'); + // getNextButton().should('not.be.disabled'); + // }); + + // it('...should show mappings warning', () => { + // // Locate Create detector button click to start + // cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true }); + // + // // Check to ensure process started + // cy.waitForPageLoad('create-detector', { + // contains: 'Define detector', + // }); + // + // // Select our pre-seeded data source (check cypressIndexDns) + // cy.get(`[data-test-subj="define-detector-select-data-source"]`) + // .find('input') + // .focus() + // .realType(cypressIndexDns); + // + // // Select threat detector type (Windows logs) + // cy.get(`input[id="dns"]`).click({ force: true }); + // + // // Select our pre-seeded data source (check cypressIndexDns) + // cy.get(`[data-test-subj="define-detector-select-data-source"]`) + // .find('input') + // .focus() + // .realType(cypressIndexWindows) + // .realPress('Enter'); + // + // cy.get('.euiCallOut') + // .should('be.visible') + // .contains( + // 'To avoid issues with field mappings, we recommend creating separate detectors for different log types.' + // ); + // }); it('...can be created', () => { createDetector(detectorName, cypressIndexDns, false); cy.contains('Detector created successfully'); }); - it('...can fail creation', () => { - createDetector(`${detectorName}_fail`, '.kibana_1', true); - cy.contains('Create detector failed.'); - }); - - it('...basic details can be edited', () => { - // Click on detector name - cy.contains(detectorName).click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); - - // Click "Edit" button in detector details - cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); - - // Confirm arrival at "Edit detector details" page - cy.waitForPageLoad('edit-detector-details', { - contains: 'Edit detector details', - }); - - // Change detector name - cy.get(`input[placeholder="Enter a name for the detector."]`) - .realClick() - .ospClear() - .realType('test detector edited'); - - // Change detector description - cy.get(`[data-test-subj="define-detector-detector-description"]`) - .focus() - .realType('Edited description'); - - // Change input source - cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - `${cypressIndexWindows}{enter}` - ); - - // Change detector scheduling - cy.get(`[data-test-subj="detector-schedule-number-select"]`).ospClear().focus().realType('10'); - cy.get(`[data-test-subj="detector-schedule-unit-select"]`).select('Hours'); - - // Save changes to detector details - cy.get(`[data-test-subj="save-basic-details-edits"]`).click({ force: true }); - - // Confirm taken to detector details page - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); - - // Verify edits are applied - cy.contains('test detector edited'); - cy.contains('Every 10 hours'); - cy.contains('Edited description'); - cy.contains(cypressIndexWindows); - }); - - it('...rules can be edited', () => { - // Ensure start on main detectors page - cy.waitForPageLoad('detectors', { - contains: 'Threat detectors', - }); - - // Click on detector name - cy.contains(detectorName).click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); - - // Confirm number of rules before edit - cy.contains('Active rules (13)'); - - // Click "Edit" button in Detector rules panel - cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); - - // Confirm arrival on "Edit detector rules" page - cy.waitForPageLoad('edit-detector-rules', { - contains: 'Edit detector rules', - }); - - // Search for specific rule - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - - // Toggle single search result to unchecked - cy.contains('table tr', cypressDNSRule).within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - // Save changes - cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ force: true }); - - // Confirm 1 rule has been removed from detector - cy.contains('Active rules (12)'); - - // Click "Edit" button in Detector rules panel - cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); - - // Confirm arrival on "Edit detector rules" page - cy.waitForPageLoad('edit-detector-rules', { - contains: 'Edit detector rules', - }); - - // Search for specific rule - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - - // Toggle single search result to checked - cy.contains('table tr', cypressDNSRule).within(() => { - cy.wait(2000); - cy.get('button').eq(1).click({ force: true }); - }); - - // Save changes - cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); - - // Confirm 1 rule has been added to detector - cy.contains('Active rules (13)'); - }); - - it('...should update field mappings if data source is changed', () => { - // Click on detector name - cy.contains(detectorName).click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); - - // Click "Edit" button in detector details - cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); - - // Confirm arrival at "Edit detector details" page - cy.waitForPageLoad('edit-detector-details', { - contains: 'Edit detector details', - }); - - cy.get('.reviewFieldMappings').should('not.exist'); - - // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .ospClear() - .focus() - .realType(cypressIndexWindows) - .realPress('Enter'); - - cy.get('.reviewFieldMappings').should('be.visible'); - cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - `${cypressIndexWindows}{enter}` - ); - }); + // it('...can fail creation', () => { + // createDetector(`${detectorName}_fail`, '.kibana_1', true); + // cy.contains('Create detector failed.'); + // }); + // + // it('...basic details can be edited', () => { + // // Click on detector name + // cy.contains(detectorName).click({ force: true }); + // cy.waitForPageLoad('detector-details', { + // contains: detectorName, + // }); + // + // // Click "Edit" button in detector details + // cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); + // + // // Confirm arrival at "Edit detector details" page + // cy.waitForPageLoad('edit-detector-details', { + // contains: 'Edit detector details', + // }); + // + // // Change detector name + // cy.get(`input[placeholder="Enter a name for the detector."]`) + // .realClick() + // .ospClear() + // .realType('test detector edited'); + // + // // Change detector description + // cy.get(`[data-test-subj="define-detector-detector-description"]`) + // .focus() + // .realType('Edited description'); + // + // // Change input source + // cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + // cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( + // `${cypressIndexWindows}{enter}` + // ); + // + // // Change detector scheduling + // cy.get(`[data-test-subj="detector-schedule-number-select"]`).ospClear().focus().realType('10'); + // cy.get(`[data-test-subj="detector-schedule-unit-select"]`).select('Hours'); + // + // // Save changes to detector details + // cy.get(`[data-test-subj="save-basic-details-edits"]`).click({ force: true }); + // + // // Confirm taken to detector details page + // cy.waitForPageLoad('detector-details', { + // contains: detectorName, + // }); + // + // // Verify edits are applied + // cy.contains('test detector edited'); + // cy.contains('Every 10 hours'); + // cy.contains('Edited description'); + // cy.contains(cypressIndexWindows); + // }); + // + // it('...rules can be edited', () => { + // // Ensure start on main detectors page + // cy.waitForPageLoad('detectors', { + // contains: 'Threat detectors', + // }); + // + // // Click on detector name + // cy.contains(detectorName).click({ force: true }); + // cy.waitForPageLoad('detector-details', { + // contains: detectorName, + // }); + // + // // Confirm number of rules before edit + // cy.contains('Active rules (13)'); + // + // // Click "Edit" button in Detector rules panel + // cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); + // + // // Confirm arrival on "Edit detector rules" page + // cy.waitForPageLoad('edit-detector-rules', { + // contains: 'Edit detector rules', + // }); + // + // // Search for specific rule + // cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); + // + // // Toggle single search result to unchecked + // cy.contains('table tr', cypressDNSRule).within(() => { + // // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. + // cy.wait(1000); + // cy.get('button').eq(1).click(); + // }); + // + // // Save changes + // cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ force: true }); + // + // // Confirm 1 rule has been removed from detector + // cy.contains('Active rules (12)'); + // + // // Click "Edit" button in Detector rules panel + // cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); + // + // // Confirm arrival on "Edit detector rules" page + // cy.waitForPageLoad('edit-detector-rules', { + // contains: 'Edit detector rules', + // }); + // + // // Search for specific rule + // cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); + // + // // Toggle single search result to checked + // cy.contains('table tr', cypressDNSRule).within(() => { + // cy.wait(2000); + // cy.get('button').eq(1).click({ force: true }); + // }); + // + // // Save changes + // cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ force: true }); + // cy.waitForPageLoad('detector-details', { + // contains: detectorName, + // }); + // + // // Confirm 1 rule has been added to detector + // cy.contains('Active rules (13)'); + // }); + + // it('...should update field mappings if data source is changed', () => { + // // Click on detector name + // cy.contains(detectorName).click({ force: true }); + // cy.waitForPageLoad('detector-details', { + // contains: detectorName, + // }); + // + // // Click "Edit" button in detector details + // cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); + // + // // Confirm arrival at "Edit detector details" page + // cy.waitForPageLoad('edit-detector-details', { + // contains: 'Edit detector details', + // }); + // + // cy.get('.reviewFieldMappings').should('not.exist'); + // + // // Change input source + // cy.get(`[data-test-subj="define-detector-select-data-source"]`) + // .find('input') + // .ospClear() + // .focus() + // .realType(cypressIndexWindows) + // .realPress('Enter'); + // + // cy.get('.reviewFieldMappings').should('be.visible'); + // cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + // cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( + // `${cypressIndexWindows}{enter}` + // ); + // }); it('...should update field mappings if rule selection is changed', () => { + cy.intercept('mappings/view').as('getMappingsView'); + // Click on detector name cy.contains(detectorName).click({ force: true }); cy.waitForPageLoad('detector-details', { @@ -390,35 +428,35 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); - cy.intercept('mappings/view').as('getMappingsView'); + cy.wait('@detectorsSearch'); // Toggle single search result to unchecked - cy.contains('table tr', cypressDNSRule).within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); + cy.get( + '[data-test-subj="edit-detector-rules-table"] table thead tr:first th:first button' + ).click(); cy.wait('@getMappingsView'); cy.get('.reviewFieldMappings').should('be.visible'); + cy.get('.sample_dns_index_settings '); + cy.contains('Automatically mapped fields (1)'); }); - it('...can be deleted', () => { - // Click on detector to be removed - cy.contains('test detector edited').click({ force: true }); - - // Confirm page - cy.waitForPageLoad('detector-details', { - contains: 'Detector details', - }); - - // Click "Actions" button, the click "Delete" - cy.get('button').contains('Actions').click({ force: true }); - cy.get('button').contains('Delete').click({ force: true }); - - // Confirm detector is deleted - cy.contains('There are no existing detectors'); - }); + // it('...can be deleted', () => { + // // Click on detector to be removed + // cy.contains('test detector edited').click({ force: true }); + // + // // Confirm page + // cy.waitForPageLoad('detector-details', { + // contains: 'Detector details', + // }); + // + // // Click "Actions" button, the click "Delete" + // cy.get('button').contains('Actions').click({ force: true }); + // cy.get('button').contains('Delete').click({ force: true }); + // + // // Confirm detector is deleted + // cy.contains('There are no existing detectors'); + // }); after(() => cy.cleanUpTests()); }); diff --git a/cypress/integration/3_alerts.spec.js b/cypress/integration/3_alerts.spec.js index 77f125384..71910cf74 100644 --- a/cypress/integration/3_alerts.spec.js +++ b/cypress/integration/3_alerts.spec.js @@ -5,7 +5,7 @@ import moment from 'moment'; import { DETECTOR_TRIGGER_TIMEOUT, OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; -import sample_index_settings from '../fixtures/sample_index_settings.json'; +import sample_index_settings from '../fixtures/sample_windows_index_settings.json'; import sample_alias_mappings from '../fixtures/sample_alias_mappings.json'; import sample_detector from '../fixtures/sample_detector.json'; import sample_document from '../fixtures/sample_document.json'; diff --git a/cypress/integration/4_findings.spec.js b/cypress/integration/4_findings.spec.js index afcc744e3..a490de9fa 100644 --- a/cypress/integration/4_findings.spec.js +++ b/cypress/integration/4_findings.spec.js @@ -5,7 +5,7 @@ import { DETECTOR_TRIGGER_TIMEOUT, OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; import sample_document from '../fixtures/sample_document.json'; -import sample_index_settings from '../fixtures/sample_index_settings.json'; +import sample_index_settings from '../fixtures/sample_windows_index_settings.json'; import sample_field_mappings from '../fixtures/sample_field_mappings.json'; import sample_detector from '../fixtures/sample_detector.json'; diff --git a/cypress/integration/5_integrations.spec.js b/cypress/integration/5_integrations.spec.js index 6aaef03a1..054295d7a 100644 --- a/cypress/integration/5_integrations.spec.js +++ b/cypress/integration/5_integrations.spec.js @@ -4,10 +4,10 @@ */ import { DETECTOR_TRIGGER_TIMEOUT, OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; -import sample_index_settings from '../fixtures/sample_index_settings.json'; +import sample_index_settings from '../fixtures/sample_windows_index_settings.json'; import sample_dns_settings from '../fixtures/integration_tests/index/create_dns_settings.json'; import windows_usb_rule_data from '../fixtures/integration_tests/rule/create_windows_usb_rule.json'; -import dns_rule_data from '../fixtures/integration_tests/rule/create_dns_rule.json'; +import dns_rule_data from '../fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json'; import usb_detector_data from '../fixtures/integration_tests/detector/create_usb_detector_data.json'; import usb_detector_data_mappings from '../fixtures/integration_tests/detector/create_usb_detector_mappings_data.json'; import dns_detector_data_mappings from '../fixtures/integration_tests/detector/create_dns_detector_mappings_data.json'; From b4a1c2ec8cad612f5c854a7dd630ada517eb9e49 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Wed, 12 Apr 2023 09:02:25 +0200 Subject: [PATCH 33/64] Update detector details component #504 Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 8d63c151f..554de83a0 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -22,6 +22,11 @@ const testMappings = { }, }; +// cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); +// cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( +// `${cypressIndexWindows}{enter}` +// ); + const cypressDNSRule = dns_name_rule_data.title; const cypressDNSTypeRule = dns_type_rule_data.title; From 5333bc627693688b275184de8de0c22e821e9834 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Thu, 13 Apr 2023 18:14:28 +0200 Subject: [PATCH 34/64] cypress tests Signed-off-by: Jovan Cvetkovic --- .../create_dns_detector_mappings_data.json | 8 +- .../create_usb_detector_mappings_data.json | 24 +-- .../index/add_dns_index_data.json | 6 +- .../index/create_dns_settings.json | 7 +- .../index/create_windows_settings.json | 16 +- .../create_dns_rule_with_name_selection.json | 2 +- .../create_dns_rule_with_type_selection.json | 2 +- .../rule/create_windows_usb_rule.json | 2 +- .../fixtures/sample_dns_index_settings.json | 6 +- .../sample_windows_index_settings.json | 16 +- cypress/integration/1_detectors.spec.js | 151 ++++++++++++------ .../AlertTriggerView/AlertTriggerView.tsx | 22 +-- 12 files changed, 133 insertions(+), 129 deletions(-) diff --git a/cypress/fixtures/integration_tests/detector/create_dns_detector_mappings_data.json b/cypress/fixtures/integration_tests/detector/create_dns_detector_mappings_data.json index e4056d577..22cca8b32 100644 --- a/cypress/fixtures/integration_tests/detector/create_dns_detector_mappings_data.json +++ b/cypress/fixtures/integration_tests/detector/create_dns_detector_mappings_data.json @@ -2,15 +2,11 @@ "properties": { "dns-answers-type": { "type": "alias", - "path": "DnsAnswerType" - }, - "dns-question-name": { - "type": "alias", - "path": "DnsQuestionName" + "path": "dns.answers.type" }, "dns-question-registered_domain": { "type": "alias", - "path": "DnsQuestionRegisteredDomain" + "path": "dns.question.registered_domain" } } } diff --git a/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json b/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json index da81361fe..f02e5a4b1 100644 --- a/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json +++ b/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json @@ -1,28 +1,12 @@ { "properties": { - "event_uid": { + "winlog-event_uid": { "type": "alias", - "path": "EventID" + "path": "winlog.event_id" }, - "windows-event_data-CommandLine": { + "winlog.provider_name": { "type": "alias", - "path": "CommandLine" - }, - "windows-hostname": { - "type": "alias", - "path": "HostName" - }, - "windows-message": { - "type": "alias", - "path": "Message" - }, - "windows-provider-name": { - "type": "alias", - "path": "Provider_Name" - }, - "windows-servicename": { - "type": "alias", - "path": "ServiceName" + "path": "winlog-provider_name" } } } diff --git a/cypress/fixtures/integration_tests/index/add_dns_index_data.json b/cypress/fixtures/integration_tests/index/add_dns_index_data.json index 35077a0f5..a0ac8147a 100644 --- a/cypress/fixtures/integration_tests/index/add_dns_index_data.json +++ b/cypress/fixtures/integration_tests/index/add_dns_index_data.json @@ -1,5 +1,5 @@ { - "DnsAnswerType": "QWE", - "DnsQuestionRegisteredDomain": "EC2AMAZ-EPWO7HKA", - "DnsQuestionName": "QWE" + "dns.answers.type": "AnswerType", + "dns.question.registered_domain": "EC2AMAZ-EPWO7HKA", + "dns.question_name": "QuestionName" } diff --git a/cypress/fixtures/integration_tests/index/create_dns_settings.json b/cypress/fixtures/integration_tests/index/create_dns_settings.json index 126659dc6..aea025a37 100644 --- a/cypress/fixtures/integration_tests/index/create_dns_settings.json +++ b/cypress/fixtures/integration_tests/index/create_dns_settings.json @@ -1,13 +1,10 @@ { "mappings": { "properties": { - "DnsAnswerType": { + "dns.answers.type": { "type": "text" }, - "DnsQuestionRegisteredDomain": { - "type": "text" - }, - "DnsQuestionName": { + "dns.question.registered_domain": { "type": "text" } } diff --git a/cypress/fixtures/integration_tests/index/create_windows_settings.json b/cypress/fixtures/integration_tests/index/create_windows_settings.json index f794e671e..480f63ba1 100644 --- a/cypress/fixtures/integration_tests/index/create_windows_settings.json +++ b/cypress/fixtures/integration_tests/index/create_windows_settings.json @@ -1,22 +1,10 @@ { "mappings": { "properties": { - "CommandLine": { - "type": "text" - }, - "EventID": { + "winlog.event_id": { "type": "integer" }, - "HostName": { - "type": "text" - }, - "Message": { - "type": "text" - }, - "Provider_Name": { - "type": "text" - }, - "ServiceName": { + "winlog.provider_name": { "type": "text" } } diff --git a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json index ae89e69da..257dc7bf0 100644 --- a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json +++ b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json @@ -16,7 +16,7 @@ } ], "log_source": "", - "detection": "selection:\n DnsQuestionName:\n - QWE\n - ASD\n - YXC\ncondition: selection", + "detection": "selection:\n dns.question_name:\n - QWE\n - ASD\n - YXC\ncondition: selection", "level": "low", "false_positives": [ { diff --git a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json index 508a09b74..4e78b9d20 100644 --- a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json +++ b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json @@ -16,7 +16,7 @@ } ], "log_source": "", - "detection": "selection:\n DnsAnswerType:\n - QWE\n - ASD\n - YXC\ncondition: selection", + "detection": "selection:\n dns.answers.type:\n - QWE\n - ASD\n - YXC\ncondition: selection", "level": "low", "false_positives": [ { diff --git a/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json b/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json index 20f59799a..3db4cf141 100644 --- a/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json +++ b/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json @@ -16,7 +16,7 @@ } ], "log_source": "", - "detection": "selection:\n EventID:\n - 2003\n - 2100\n - 2102\ncondition: selection", + "detection": "selection:\n winlog.event_id:\n - 2003\n - 2100\n - 2102\ncondition: selection", "level": "low", "false_positives": [ { diff --git a/cypress/fixtures/sample_dns_index_settings.json b/cypress/fixtures/sample_dns_index_settings.json index 3ccc2b613..5f8f1694a 100644 --- a/cypress/fixtures/sample_dns_index_settings.json +++ b/cypress/fixtures/sample_dns_index_settings.json @@ -1,13 +1,13 @@ { "mappings": { "properties": { - "DnsQuestionName": { + "dns.question_name": { "type": "text" }, - "DnsAnswerType": { + "dns.answers.type": { "type": "text" }, - "DnsQuestionRegisteredDomain": { + "dns.question.registered_domain": { "type": "text" } } diff --git a/cypress/fixtures/sample_windows_index_settings.json b/cypress/fixtures/sample_windows_index_settings.json index f794e671e..480f63ba1 100644 --- a/cypress/fixtures/sample_windows_index_settings.json +++ b/cypress/fixtures/sample_windows_index_settings.json @@ -1,22 +1,10 @@ { "mappings": { "properties": { - "CommandLine": { - "type": "text" - }, - "EventID": { + "winlog.event_id": { "type": "integer" }, - "HostName": { - "type": "text" - }, - "Message": { - "type": "text" - }, - "Provider_Name": { - "type": "text" - }, - "ServiceName": { + "winlog.provider_name": { "type": "text" } } diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 81a33f2cc..46d23420e 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -4,23 +4,30 @@ */ import { OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; -import sample_index_settings from '../fixtures/sample_index_settings.json'; -import dns_rule_data from '../fixtures/integration_tests/rule/create_dns_rule.json'; +import sample_windows_index_settings from '../fixtures/sample_windows_index_settings.json'; +import sample_dns_index_settings from '../fixtures/sample_dns_index_settings.json'; +import dns_name_rule_data from '../fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json'; +import dns_type_rule_data from '../fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json'; + +const cypressIndexDns = 'cypress-index-dns'; +const cypressIndexWindows = 'cypress-index-windows'; +const detectorName = 'test detector'; const testMappings = { properties: { 'dns-question-name': { type: 'alias', - path: 'DnsQuestionName', + path: 'dns.question_name', }, }, }; -const cypressDNSRule = dns_rule_data.title; +const cypressDNSRule = dns_name_rule_data.title; +const cypressDNSTypeRule = dns_type_rule_data.title; -const cypressIndexDns = 'cypress-index-dns'; -const cypressIndexWindows = 'cypress-index-windows'; -const detectorName = 'test detector'; +const getNameField = () => cy.get(`input[placeholder="Enter a name for the detector."]`); +const getDataSourceField = () => cy.get(`.euiComboBox__input input`); +const getNextButton = () => cy.get('.euiButton').filter(':contains("Next")'); const createDetector = (detectorName, dataSource, expectFailure) => { // Locate Create detector button click to start @@ -32,13 +39,11 @@ const createDetector = (detectorName, dataSource, expectFailure) => { }); // Enter a name for the detector in the appropriate input - cy.get(`input[placeholder="Enter a name for the detector."]`).focus().realType(detectorName); + cy.get(`input[placeholder="Enter a name for the detector."]`).focus().type(detectorName); // Select our pre-seeded data source (check cypressIndexDns) - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .focus() - .realType(dataSource); + // cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type(`${dataSource}{enter}`); cy.intercept({ pathname: '/_plugins/_security_analytics/rules/_search', @@ -60,7 +65,12 @@ const createDetector = (detectorName, dataSource, expectFailure) => { }); // Check that correct page now showing - cy.contains('Configure field mapping'); + cy.contains('Configure field mapping - optional'); + cy.get('[aria-controls="mappedTitleFieldsAccordion"]').then(($btn) => { + if ($btn && $btn[0] && $btn[0].getAttribute('aria-expanded') === 'false') { + $btn[0].click(); + } + }); if (!expectFailure) { // Select appropriate names to map fields to @@ -82,14 +92,10 @@ const createDetector = (detectorName, dataSource, expectFailure) => { // Type name of new trigger cy.get(`input[placeholder="Enter a name to describe the alert condition"]`) .focus() - .realType('test_trigger'); + .type('test_trigger'); // Type in (or select) tags for the alert condition - cy.get(`[data-test-subj="alert-tags-combo-box"]`) - .find('input') - .focus() - .realType('attack.defense_evasion') - .realPress('Enter'); + cy.get(`[data-test-subj="alert-tags-combo-box"]`).type(`attack.defense_evasion{enter}`); // Select applicable severity levels cy.get(`[data-test-subj="security-levels-combo-box"]`).click({ force: true }); @@ -121,6 +127,7 @@ const createDetector = (detectorName, dataSource, expectFailure) => { // Create the detector cy.get('button').contains('Create').click({ force: true }); + cy.waitForPageLoad('detector-details', { contains: detectorName, }); @@ -147,10 +154,10 @@ describe('Detectors', () => { before(() => { cy.cleanUpTests(); - cy.createIndex(cypressIndexWindows, sample_index_settings); + cy.createIndex(cypressIndexWindows, sample_windows_index_settings); // Create test index - cy.createIndex(cypressIndexDns, sample_index_settings).then(() => + cy.createIndex(cypressIndexDns, sample_dns_index_settings).then(() => cy .request('POST', '_plugins/_security_analytics/rules/_search?prePackaged=true', { from: 0, @@ -165,7 +172,8 @@ describe('Detectors', () => { .should('have.property', 'status', 200) ); - cy.createRule(dns_rule_data); + cy.createRule(dns_name_rule_data); + cy.createRule(dns_type_rule_data); }); beforeEach(() => { @@ -180,6 +188,37 @@ describe('Detectors', () => { }); }); + it('...should validate form', () => { + // Locate Create detector button click to start + cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true }); + + // Check to ensure process started + cy.waitForPageLoad('create-detector', { + contains: 'Define detector', + }); + + getNextButton().should('be.disabled'); + + getNameField().should('be.empty'); + getNameField().type('text').blur(); + + cy.get('.euiFormErrorText').contains( + 'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.' + ); + + getNameField().type(' and more text').blur(); + cy.get('.euiFormErrorText').should('not.exist'); + getNextButton().should('be.disabled'); + + getDataSourceField().focus().blur(); + cy.get('.euiFormErrorText').contains('Select an input source'); + getNextButton().should('be.disabled'); + + getDataSourceField().type(`${cypressIndexDns}{enter}`); + cy.get('.euiFormErrorText').should('not.exist'); + getNextButton().should('not.be.disabled'); + }); + it('...should show mappings warning', () => { // Locate Create detector button click to start cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true }); @@ -190,20 +229,19 @@ describe('Detectors', () => { }); // Select our pre-seeded data source (check cypressIndexDns) - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .focus() - .realType(cypressIndexDns); + + // cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( + `${cypressIndexDns}{enter}` + ); // Select threat detector type (Windows logs) cy.get(`input[id="dns"]`).click({ force: true }); // Select our pre-seeded data source (check cypressIndexDns) - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .focus() - .realType(cypressIndexWindows) - .realPress('Enter'); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( + `${cypressIndexWindows}{enter}` + ); cy.get('.euiCallOut') .should('be.visible') @@ -239,14 +277,13 @@ describe('Detectors', () => { // Change detector name cy.get(`input[placeholder="Enter a name for the detector."]`) - .realClick() - .ospClear() - .realType('test detector edited'); + .type('{selectall}{backspace}') + .type('test detector edited'); // Change detector description cy.get(`[data-test-subj="define-detector-detector-description"]`) .focus() - .realType('Edited description'); + .type('Edited description'); // Change input source cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); @@ -255,7 +292,10 @@ describe('Detectors', () => { ); // Change detector scheduling - cy.get(`[data-test-subj="detector-schedule-number-select"]`).ospClear().focus().realType('10'); + cy.get(`[data-test-subj="detector-schedule-number-select"]`) + .focus() + .type('{selectall}{backspace}') + .type('10'); cy.get(`[data-test-subj="detector-schedule-unit-select"]`).select('Hours'); // Save changes to detector details @@ -271,6 +311,15 @@ describe('Detectors', () => { cy.contains('Every 10 hours'); cy.contains('Edited description'); cy.contains(cypressIndexWindows); + + // Change input source + cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); + cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( + `${cypressIndexDns}{enter}` + ); + cy.get(`[data-test-subj="save-basic-details-edits"]`).click({ force: true }); + cy.contains(cypressIndexDns); }); it('...rules can be edited', () => { @@ -286,7 +335,7 @@ describe('Detectors', () => { }); // Confirm number of rules before edit - cy.contains('Active rules (13)'); + cy.contains('Active rules (14)'); // Click "Edit" button in Detector rules panel cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); @@ -310,7 +359,7 @@ describe('Detectors', () => { cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ force: true }); // Confirm 1 rule has been removed from detector - cy.contains('Active rules (12)'); + cy.contains('Active rules (13)'); // Click "Edit" button in Detector rules panel cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); @@ -336,10 +385,10 @@ describe('Detectors', () => { }); // Confirm 1 rule has been added to detector - cy.contains('Active rules (13)'); + cy.contains('Active rules (14)'); }); - it('...should show field mappings if data source is changed', () => { + it('...should update field mappings if data source is changed', () => { // Click on detector name cy.contains(detectorName).click({ force: true }); cy.waitForPageLoad('detector-details', { @@ -358,14 +407,20 @@ describe('Detectors', () => { // Change input source cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + + cy.get('.reviewFieldMappings').should('be.visible'); + cy.contains('Automatically mapped fields (0)'); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - `${cypressIndexWindows}{enter}` + `${cypressIndexDns}{enter}` ); - cy.get('.reviewFieldMappings').should('be.visible'); + cy.contains('Automatically mapped fields (3)'); }); - it('...should show field mappings if rule selection is changed', () => { + it('...should update field mappings if rule selection is changed', () => { + cy.intercept('mappings/view').as('getMappingsView'); + // Click on detector name cy.contains(detectorName).click({ force: true }); cy.waitForPageLoad('detector-details', { @@ -382,14 +437,16 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); - cy.intercept('mappings/view').as('getMappingsView'); + cy.wait('@detectorsSearch'); - cy.get('table th').within(() => { - cy.get('button').first().click({ force: true }); - }); + // Toggle single search result to unchecked + cy.get( + '[data-test-subj="edit-detector-rules-table"] table thead tr:first th:first button' + ).click(); cy.wait('@getMappingsView'); cy.get('.reviewFieldMappings').should('be.visible'); + cy.contains('Automatically mapped fields (3)'); }); it('...can be deleted', () => { diff --git a/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx b/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx index a3db9f2dc..c76aba611 100644 --- a/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx +++ b/public/pages/Detectors/components/AlertTriggerView/AlertTriggerView.tsx @@ -64,20 +64,14 @@ export const AlertTriggerView: React.FC = ({
If any detection rule matches
- {createTextDetailsGroup( - [ - { label: 'Log type', content: `${types[0]}` || DEFAULT_EMPTY_DATA }, - { label: 'Rule names', content: conditionRuleNames.join('\n') || DEFAULT_EMPTY_DATA }, - ], - 3 - )} - {createTextDetailsGroup( - [ - { label: 'Rule severities', content: sev_levels.join('\n') || DEFAULT_EMPTY_DATA }, - { label: 'Tags', content: tags.join('\n') || DEFAULT_EMPTY_DATA }, - ], - 3 - )} + {createTextDetailsGroup([ + { label: 'Log type', content: `${types[0]}` || DEFAULT_EMPTY_DATA }, + { label: 'Rule names', content: conditionRuleNames.join('\n') || DEFAULT_EMPTY_DATA }, + ])} + {createTextDetailsGroup([ + { label: 'Rule severities', content: sev_levels.join('\n') || DEFAULT_EMPTY_DATA }, + { label: 'Tags', content: tags.join('\n') || DEFAULT_EMPTY_DATA }, + ])} From c19365f52021d308d6371f162ad197890244d4d9 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Thu, 13 Apr 2023 19:21:43 +0200 Subject: [PATCH 35/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 46d23420e..449503d01 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -418,7 +418,7 @@ describe('Detectors', () => { cy.contains('Automatically mapped fields (3)'); }); - it('...should update field mappings if rule selection is changed', () => { + it('...should show field mappings if rule selection is changed', () => { cy.intercept('mappings/view').as('getMappingsView'); // Click on detector name From 51324635c782d37a7a59de240f2609b2881be9da Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Thu, 13 Apr 2023 20:15:49 +0200 Subject: [PATCH 36/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 449503d01..84c9b9f94 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -42,7 +42,6 @@ const createDetector = (detectorName, dataSource, expectFailure) => { cy.get(`input[placeholder="Enter a name for the detector."]`).focus().type(detectorName); // Select our pre-seeded data source (check cypressIndexDns) - // cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type(`${dataSource}{enter}`); cy.intercept({ @@ -286,7 +285,9 @@ describe('Detectors', () => { .type('Edited description'); // Change input source - cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).within(() => { + cy.get('.euiFormControlLayoutClearButton').click({ force: true }); + }); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexWindows}{enter}` ); @@ -314,7 +315,9 @@ describe('Detectors', () => { // Change input source cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); - cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).within(() => { + cy.get('.euiFormControlLayoutClearButton').click({ force: true }); + }); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexDns}{enter}` ); @@ -406,7 +409,9 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); // Change input source - cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).within(() => { + cy.get('.euiFormControlLayoutClearButton').click({ force: true }); + }); cy.get('.reviewFieldMappings').should('be.visible'); cy.contains('Automatically mapped fields (0)'); @@ -446,7 +451,6 @@ describe('Detectors', () => { cy.wait('@getMappingsView'); cy.get('.reviewFieldMappings').should('be.visible'); - cy.contains('Automatically mapped fields (3)'); }); it('...can be deleted', () => { From c960a567e8ed8ec0c8bbad201700b2b602c1c409 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Thu, 13 Apr 2023 20:21:20 +0200 Subject: [PATCH 37/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 84c9b9f94..011358fe4 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -447,7 +447,7 @@ describe('Detectors', () => { // Toggle single search result to unchecked cy.get( '[data-test-subj="edit-detector-rules-table"] table thead tr:first th:first button' - ).click(); + ).click({ force: true }); cy.wait('@getMappingsView'); cy.get('.reviewFieldMappings').should('be.visible'); From 2788f6ceebb476bf1e0e8075e472d1169c08afe8 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Thu, 13 Apr 2023 21:05:32 +0200 Subject: [PATCH 38/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 011358fe4..d36dcaa06 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -285,9 +285,9 @@ describe('Detectors', () => { .type('Edited description'); // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`).within(() => { - cy.get('.euiFormControlLayoutClearButton').click({ force: true }); - }); + cy.get( + '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' + ).click({ force: true }); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexWindows}{enter}` ); @@ -315,9 +315,9 @@ describe('Detectors', () => { // Change input source cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); - cy.get(`[data-test-subj="define-detector-select-data-source"]`).within(() => { - cy.get('.euiFormControlLayoutClearButton').click({ force: true }); - }); + cy.get( + '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' + ).click({ force: true }); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexDns}{enter}` ); @@ -409,17 +409,15 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`).within(() => { - cy.get('.euiFormControlLayoutClearButton').click({ force: true }); - }); - - cy.get('.reviewFieldMappings').should('be.visible'); - cy.contains('Automatically mapped fields (0)'); + cy.get( + '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' + ).click({ force: true }); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexDns}{enter}` ); + cy.get('.reviewFieldMappings').should('be.visible'); cy.contains('Automatically mapped fields (3)'); }); From f9674c2b307dc18327dc664494b840912ce8f433 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Thu, 13 Apr 2023 23:10:39 +0200 Subject: [PATCH 39/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index d36dcaa06..f344983c3 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -409,10 +409,7 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); // Change input source - cy.get( - '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' - ).click({ force: true }); - + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type('{backspace}'); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexDns}{enter}` ); @@ -422,6 +419,14 @@ describe('Detectors', () => { }); it('...should show field mappings if rule selection is changed', () => { + cy.intercept('POST', 'rules/_search?prePackaged=true', { + delay: 5000, + }).as('getPrePackagedRules'); + + cy.intercept('POST', 'rules/_search?prePackaged=false', { + delay: 5000, + }).as('getCustomRules'); + cy.intercept('mappings/view').as('getMappingsView'); // Click on detector name @@ -441,6 +446,8 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); cy.wait('@detectorsSearch'); + cy.wait('@getCustomRules'); + cy.wait('@getPrePackagedRules'); // Toggle single search result to unchecked cy.get( From 97c29be6b1a8e90eee07826191adadfe0ae0643d Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sat, 15 Apr 2023 20:59:22 +0200 Subject: [PATCH 40/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index f344983c3..8b23b5055 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -409,7 +409,10 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type('{backspace}'); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).within(($el) => { + cy.get('.euiFormControlLayoutClearButton').click({ force: true }); + cy.get($el).find('input').type('{selectall}{backspace}'); + }); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexDns}{enter}` ); From 6f7766e13190d768e56ba62c66836d6238d139ae Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sat, 15 Apr 2023 21:09:14 +0200 Subject: [PATCH 41/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 8b23b5055..1ed70c780 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -23,7 +23,7 @@ const testMappings = { }; const cypressDNSRule = dns_name_rule_data.title; -const cypressDNSTypeRule = dns_type_rule_data.title; +// const cypressDNSTypeRule = dns_type_rule_data.title; const getNameField = () => cy.get(`input[placeholder="Enter a name for the detector."]`); const getDataSourceField = () => cy.get(`.euiComboBox__input input`); From db214d12cb9371a209ee1d12f01e5ee28bfacd32 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sat, 15 Apr 2023 21:40:16 +0200 Subject: [PATCH 42/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 1ed70c780..891167dba 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -409,10 +409,9 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`).within(($el) => { - cy.get('.euiFormControlLayoutClearButton').click({ force: true }); - cy.get($el).find('input').type('{selectall}{backspace}'); - }); + cy.get( + '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' + ).click({ force: true }); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexDns}{enter}` ); From 915e490ce85d419f331755c201d3b465c69ad725 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sat, 15 Apr 2023 22:27:30 +0200 Subject: [PATCH 43/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 891167dba..a70c00191 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -409,9 +409,11 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); // Change input source - cy.get( - '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' - ).click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).within(($el) => { + const clearBtn = $el.find('.euiFormControlLayoutClearButton__icon'); + clearBtn[0] && cy.get(clearBtn[0]).click({ force: true }); + cy.wait(1000); + }); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexDns}{enter}` ); From 8b98a736437d1baae043225ca09f31bc5767a509 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 17 Apr 2023 20:57:08 +0200 Subject: [PATCH 44/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index a70c00191..b3800976e 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -409,11 +409,16 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('not.exist'); // Change input source - cy.get(`[data-test-subj="define-detector-select-data-source"]`).within(($el) => { - const clearBtn = $el.find('.euiFormControlLayoutClearButton__icon'); - clearBtn[0] && cy.get(clearBtn[0]).click({ force: true }); - cy.wait(1000); - }); + cy.get( + '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' + ).click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( + `${cypressIndexWindows}{enter}` + ); + + cy.get( + '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' + ).click({ force: true }); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexDns}{enter}` ); From 9a76799ae6c993cd3b8617471ca2d5d7f7aeb836 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 17 Apr 2023 23:10:00 +0200 Subject: [PATCH 45/64] cypress tests Signed-off-by: Jovan Cvetkovic --- .../create_dns_detector_mappings_data.json | 4 ++ .../create_usb_detector_mappings_data.json | 2 +- .../create_dns_rule_with_name_selection.json | 2 +- cypress/fixtures/sample_alias_mappings.json | 12 +----- cypress/fixtures/sample_document.json | 38 +------------------ cypress/fixtures/sample_field_mappings.json | 24 +----------- cypress/integration/1_detectors.spec.js | 10 ++--- 7 files changed, 14 insertions(+), 78 deletions(-) diff --git a/cypress/fixtures/integration_tests/detector/create_dns_detector_mappings_data.json b/cypress/fixtures/integration_tests/detector/create_dns_detector_mappings_data.json index 22cca8b32..6f9f869ea 100644 --- a/cypress/fixtures/integration_tests/detector/create_dns_detector_mappings_data.json +++ b/cypress/fixtures/integration_tests/detector/create_dns_detector_mappings_data.json @@ -4,6 +4,10 @@ "type": "alias", "path": "dns.answers.type" }, + "dns-question-name": { + "type": "alias", + "path": "dns.question.name" + }, "dns-question-registered_domain": { "type": "alias", "path": "dns.question.registered_domain" diff --git a/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json b/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json index f02e5a4b1..904a9457c 100644 --- a/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json +++ b/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json @@ -1,6 +1,6 @@ { "properties": { - "winlog-event_uid": { + "winlog-event_id": { "type": "alias", "path": "winlog.event_id" }, diff --git a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json index 257dc7bf0..55020ae71 100644 --- a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json +++ b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json @@ -16,7 +16,7 @@ } ], "log_source": "", - "detection": "selection:\n dns.question_name:\n - QWE\n - ASD\n - YXC\ncondition: selection", + "detection": "selection:\n dns-question-name:\n - QWE\n - ASD\n - YXC\ncondition: selection", "level": "low", "false_positives": [ { diff --git a/cypress/fixtures/sample_alias_mappings.json b/cypress/fixtures/sample_alias_mappings.json index cf08cc696..e0a1a5f88 100644 --- a/cypress/fixtures/sample_alias_mappings.json +++ b/cypress/fixtures/sample_alias_mappings.json @@ -1,16 +1,8 @@ { "properties": { - "source_ip": { + "winlog-event_id": { "type": "alias", - "path": "src_ip" - }, - "windows-event_data-CommandLine": { - "path": "CommandLine", - "type": "alias" - }, - "event_uid": { - "path": "EventID", - "type": "alias" + "path": "winlog.event_id" } } } diff --git a/cypress/fixtures/sample_document.json b/cypress/fixtures/sample_document.json index d23b31895..f8b8b4e2e 100644 --- a/cypress/fixtures/sample_document.json +++ b/cypress/fixtures/sample_document.json @@ -1,39 +1,3 @@ { - "EventTime": "2020-02-04T14:59:39.343541+00:00", - "HostName": "EC2AMAZ-EPO7HKA", - "Keywords": "9223372036854775808", - "SeverityValue": 2, - "Severity": "INFO", - "EventID": 2003, - "SourceName": "Microsoft-Windows-Sysmon", - "ProviderGuid": "{5770385F-C22A-43E0-BF4C-06F5698FFBD9}", - "Version": 5, - "TaskValue": 22, - "OpcodeValue": 0, - "RecordNumber": 9532, - "ExecutionProcessID": 1996, - "ExecutionThreadID": 2616, - "Channel": "Microsoft-Windows-Sysmon/Operational", - "Domain": "NT AUTHORITY", - "AccountName": "SYSTEM", - "UserID": "S-1-5-18", - "AccountType": "User", - "Message": "Dns query:\r\nRuleName: \r\nUtcTime: 2020-02-04 14:59:38.349\r\nProcessGuid: {b3c285a4-3cda-5dc0-0000-001077270b00}\r\nProcessId: 1904\r\nQueryName: EC2AMAZ-EPO7HKA\r\nQueryStatus: 0\r\nQueryResults: 172.31.46.38;\r\nImage: C:\\Program Files\\nxlog\\nxlog.exe", - "Category": "Dns query (rule: DnsQuery)", - "Opcode": "Info", - "UtcTime": "2020-02-04 14:59:38.349", - "ProcessGuid": "{b3c285a4-3cda-5dc0-0000-001077270b00}", - "ProcessId": "1904", - "QueryName": "EC2AMAZ-EPO7HKA", - "QueryStatus": "0", - "QueryResults": "172.31.46.38;", - "Image": "C:\\Program Files\\nxlog\\regsvr32.exe", - "EventReceivedTime": "2020-02-04T14:59:40.780905+00:00", - "SourceModuleName": "in", - "SourceModuleType": "im_msvistalog", - "CommandLine": "eachtest", - "Initiated": "true", - "Provider_Name": "Microsoft-Windows-Kernel-General", - "TargetObject": "\\SOFTWARE\\Microsoft\\Office\\Outlook\\Security", - "EventType": "SetValue" + "winlog.event_id": "2003" } diff --git a/cypress/fixtures/sample_field_mappings.json b/cypress/fixtures/sample_field_mappings.json index 6e8d728fe..e0fd397c7 100644 --- a/cypress/fixtures/sample_field_mappings.json +++ b/cypress/fixtures/sample_field_mappings.json @@ -1,27 +1,7 @@ { "properties": { - "windows-hostname": { - "type": "alias", - "path": "HostName" - }, - "windows-message": { - "type": "alias", - "path": "Message" - }, - "windows-provider-name": { - "type": "alias", - "path": "Provider_Name" - }, - "windows-servicename": { - "type": "alias", - "path": "ServiceName" - }, - "windows-event_data-CommandLine": { - "path": "CommandLine", - "type": "alias" - }, - "event_uid": { - "path": "EventID", + "winlog-event_uid": { + "path": "winlog.event_id", "type": "alias" } } diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index b3800976e..d4e3df546 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -412,13 +412,9 @@ describe('Detectors', () => { cy.get( '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' ).click({ force: true }); - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - `${cypressIndexWindows}{enter}` - ); - - cy.get( - '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' - ).click({ force: true }); + cy.get(`[data-test-subj="define-detector-select-data-source"]`) + .find('input') + .should('not.have.value'); cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( `${cypressIndexDns}{enter}` ); From 35b9d90da8ed947693b7883a285d3efa05cb4e96 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 18 Apr 2023 01:22:38 +0200 Subject: [PATCH 46/64] [BUG] No space between the detector details and the rule panel #522 [BUG] A rule flyout without references have an empty link #521 [FEATURE] Update header size to use euiTitle--small #520 Signed-off-by: Jovan Cvetkovic --- .../create_usb_detector_mappings_data.json | 4 +- .../index/add_dns_index_data.json | 2 +- .../index/add_windows_index_data.json | 38 +------------------ .../index/create_dns_settings.json | 3 ++ .../create_dns_rule_with_name_selection.json | 2 +- .../create_dns_rule_with_type_selection.json | 2 +- .../rule/create_windows_usb_rule.json | 2 +- cypress/fixtures/sample_detector.json | 2 +- .../fixtures/sample_dns_index_settings.json | 2 +- cypress/fixtures/sample_field_mappings.json | 2 +- cypress/integration/1_detectors.spec.js | 20 ---------- 11 files changed, 13 insertions(+), 66 deletions(-) diff --git a/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json b/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json index 904a9457c..0cad430bc 100644 --- a/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json +++ b/cypress/fixtures/integration_tests/detector/create_usb_detector_mappings_data.json @@ -4,9 +4,9 @@ "type": "alias", "path": "winlog.event_id" }, - "winlog.provider_name": { + "winlog-provider_name": { "type": "alias", - "path": "winlog-provider_name" + "path": "winlog.provider_name" } } } diff --git a/cypress/fixtures/integration_tests/index/add_dns_index_data.json b/cypress/fixtures/integration_tests/index/add_dns_index_data.json index a0ac8147a..901c7c3e3 100644 --- a/cypress/fixtures/integration_tests/index/add_dns_index_data.json +++ b/cypress/fixtures/integration_tests/index/add_dns_index_data.json @@ -1,5 +1,5 @@ { "dns.answers.type": "AnswerType", "dns.question.registered_domain": "EC2AMAZ-EPWO7HKA", - "dns.question_name": "QuestionName" + "dns.question.name": "QuestionName" } diff --git a/cypress/fixtures/integration_tests/index/add_windows_index_data.json b/cypress/fixtures/integration_tests/index/add_windows_index_data.json index c449c7584..f8b8b4e2e 100644 --- a/cypress/fixtures/integration_tests/index/add_windows_index_data.json +++ b/cypress/fixtures/integration_tests/index/add_windows_index_data.json @@ -1,39 +1,3 @@ { - "EventTime": "2020-02-04T14:59:39.343541+00:00", - "HostName": "EC2AMAZ-EPO7HKA", - "Keywords": "9223372036854775808", - "SeverityValue": 2, - "Severity": "ERROR", - "EventID": 2003, - "SourceName": "Microsoft-Windows-Sysmon", - "ProviderGuid": "{5770385F-C22A-43E0-BF4C-06F5698FFBD9}", - "Version": 5, - "TaskValue": 22, - "OpcodeValue": 0, - "RecordNumber": 9532, - "ExecutionProcessID": 1996, - "ExecutionThreadID": 2616, - "Channel": "Microsoft-Windows-Sysmon/Operational", - "Domain": "NT AUTHORITY", - "AccountName": "SYSTEM", - "UserID": "S-1-5-18", - "AccountType": "User", - "Message": "Dns query:\r\nRuleName: \r\nUtcTime: 2020-02-04 14:59:38.349\r\nProcessGuid: {b3c285a4-3cda-5dc0-0000-001077270b00}\r\nProcessId: 1904\r\nQueryName: EC2AMAZ-EPO7HKA\r\nQueryStatus: 0\r\nQueryResults: 172.31.46.38;\r\nImage: C:\\Program Files\\nxlog\\nxlog.exe", - "Category": "Dns query (rule: DnsQuery)", - "Opcode": "Info", - "UtcTime": "2020-02-04 14:59:38.349", - "ProcessGuid": "{b3c285a4-3cda-5dc0-0000-001077270b00}", - "ProcessId": "1904", - "QueryName": "EC2AMAZ-EPO7HKA", - "QueryStatus": "0", - "QueryResults": "172.31.46.38;", - "Image": "C:\\Program Files\\nxlog\\regsvr32.exe", - "EventReceivedTime": "2020-02-04T14:59:40.780905+00:00", - "SourceModuleName": "in", - "SourceModuleType": "im_msvistalog", - "CommandLine": "eachtest", - "Initiated": "true", - "Provider_Name": "Service_ws_Control_ws_Manager", - "TargetObject": "\\SOFTWARE\\Microsoft\\Office\\Outlook\\Security", - "EventType": "SetValue" + "winlog.event_id": "2003" } diff --git a/cypress/fixtures/integration_tests/index/create_dns_settings.json b/cypress/fixtures/integration_tests/index/create_dns_settings.json index aea025a37..970a6089a 100644 --- a/cypress/fixtures/integration_tests/index/create_dns_settings.json +++ b/cypress/fixtures/integration_tests/index/create_dns_settings.json @@ -4,6 +4,9 @@ "dns.answers.type": { "type": "text" }, + "dns.question.name": { + "type": "text" + }, "dns.question.registered_domain": { "type": "text" } diff --git a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json index 55020ae71..98e8573d1 100644 --- a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json +++ b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json @@ -16,7 +16,7 @@ } ], "log_source": "", - "detection": "selection:\n dns-question-name:\n - QWE\n - ASD\n - YXC\ncondition: selection", + "detection": "selection:\n dns-question-name:\n - QuestionName\ncondition: selection", "level": "low", "false_positives": [ { diff --git a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json index 4e78b9d20..a7335e830 100644 --- a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json +++ b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json @@ -16,7 +16,7 @@ } ], "log_source": "", - "detection": "selection:\n dns.answers.type:\n - QWE\n - ASD\n - YXC\ncondition: selection", + "detection": "selection:\n dns-answers-type:\n - AnswerType\ncondition: selection", "level": "low", "false_positives": [ { diff --git a/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json b/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json index 3db4cf141..7c84a69a4 100644 --- a/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json +++ b/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json @@ -16,7 +16,7 @@ } ], "log_source": "", - "detection": "selection:\n winlog.event_id:\n - 2003\n - 2100\n - 2102\ncondition: selection", + "detection": "selection:\n winlog-event_id:\n - 2003\n - 2100\n - 2102\ncondition: selection", "level": "low", "false_positives": [ { diff --git a/cypress/fixtures/sample_detector.json b/cypress/fixtures/sample_detector.json index 67eca1110..76d129300 100644 --- a/cypress/fixtures/sample_detector.json +++ b/cypress/fixtures/sample_detector.json @@ -27,7 +27,7 @@ "triggers": [ { "name": "sample_alert_condition", - "sev_levels": [], + "sev_levels": ["low"], "tags": [], "actions": [ { diff --git a/cypress/fixtures/sample_dns_index_settings.json b/cypress/fixtures/sample_dns_index_settings.json index 5f8f1694a..02b01e771 100644 --- a/cypress/fixtures/sample_dns_index_settings.json +++ b/cypress/fixtures/sample_dns_index_settings.json @@ -1,7 +1,7 @@ { "mappings": { "properties": { - "dns.question_name": { + "dns.question.name": { "type": "text" }, "dns.answers.type": { diff --git a/cypress/fixtures/sample_field_mappings.json b/cypress/fixtures/sample_field_mappings.json index e0fd397c7..ff4eb1830 100644 --- a/cypress/fixtures/sample_field_mappings.json +++ b/cypress/fixtures/sample_field_mappings.json @@ -1,6 +1,6 @@ { "properties": { - "winlog-event_uid": { + "winlog-event_id": { "path": "winlog.event_id", "type": "alias" } diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index d4e3df546..968b19062 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -71,17 +71,6 @@ const createDetector = (detectorName, dataSource, expectFailure) => { } }); - if (!expectFailure) { - // Select appropriate names to map fields to - for (let field_name in testMappings.properties) { - const mappedTo = testMappings.properties[field_name].path; - - cy.contains('tr', field_name).within(() => { - cy.get(`[data-test-subj="detector-field-mappings-select"]`).click().type(mappedTo); - }); - } - } - // Click Next button to continue cy.get('button').contains('Next').click({ force: true }); @@ -109,15 +98,6 @@ const createDetector = (detectorName, dataSource, expectFailure) => { // Confirm field mappings registered cy.contains('Field mapping'); - if (!expectFailure) { - for (let field in testMappings.properties) { - const mappedTo = testMappings.properties[field].path; - - cy.contains(field); - cy.contains(mappedTo); - } - } - // Confirm entries user has made cy.contains('Detector details'); cy.contains(detectorName); From 37b948339c8ce1c36587785c3da80e598cbdebc9 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 18 Apr 2023 08:01:11 +0200 Subject: [PATCH 47/64] [BUG] No space between the detector details and the rule panel #522 [BUG] A rule flyout without references have an empty link #521 [FEATURE] Update header size to use euiTitle--small #520 Signed-off-by: Jovan Cvetkovic --- .../rule/create_windows_usb_rule.json | 2 +- cypress/fixtures/sample_detector.json | 8 ++++++-- cypress/fixtures/sample_document.json | 2 +- cypress/integration/3_alerts.spec.js | 14 ++++++++++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json b/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json index 7c84a69a4..3db4cf141 100644 --- a/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json +++ b/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json @@ -16,7 +16,7 @@ } ], "log_source": "", - "detection": "selection:\n winlog-event_id:\n - 2003\n - 2100\n - 2102\ncondition: selection", + "detection": "selection:\n winlog.event_id:\n - 2003\n - 2100\n - 2102\ncondition: selection", "level": "low", "false_positives": [ { diff --git a/cypress/fixtures/sample_detector.json b/cypress/fixtures/sample_detector.json index 76d129300..7c92db523 100644 --- a/cypress/fixtures/sample_detector.json +++ b/cypress/fixtures/sample_detector.json @@ -20,7 +20,11 @@ "id": "1a4bd6e3-4c6e-405d-a9a3-53a116e341d4" } ], - "custom_rules": [] + "custom_rules": [ + { + "id": "" + } + ] } } ], @@ -51,7 +55,7 @@ ], "types": ["windows"], "severity": "4", - "ids": ["1a4bd6e3-4c6e-405d-a9a3-53a116e341d4"] + "ids": [] } ] } diff --git a/cypress/fixtures/sample_document.json b/cypress/fixtures/sample_document.json index f8b8b4e2e..521d2f677 100644 --- a/cypress/fixtures/sample_document.json +++ b/cypress/fixtures/sample_document.json @@ -1,3 +1,3 @@ { - "winlog.event_id": "2003" + "winlog.event_id": 2003 } diff --git a/cypress/integration/3_alerts.spec.js b/cypress/integration/3_alerts.spec.js index 71910cf74..1b7ef92f0 100644 --- a/cypress/integration/3_alerts.spec.js +++ b/cypress/integration/3_alerts.spec.js @@ -7,8 +7,9 @@ import moment from 'moment'; import { DETECTOR_TRIGGER_TIMEOUT, OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; import sample_index_settings from '../fixtures/sample_windows_index_settings.json'; import sample_alias_mappings from '../fixtures/sample_alias_mappings.json'; -import sample_detector from '../fixtures/sample_detector.json'; +import sample_detector from '../fixtures/integration_tests/detector/create_usb_detector_data.json'; import sample_document from '../fixtures/sample_document.json'; +import windows_rule_data from '../fixtures/integration_tests/rule/create_windows_usb_rule.json'; const testIndex = 'sample_alerts_spec_cypress_test_index'; const testDetectorName = 'alerts_spec_cypress_test_detector'; @@ -53,7 +54,16 @@ describe('Alerts', () => { ) // Create test detector - .then(() => cy.createDetector(testDetector)) + .then(() => { + cy.createRule(windows_rule_data) + .then((response) => { + testDetector.inputs[0].detector_input.custom_rules[0].id = response.body.response._id; + testDetector.triggers[0].ids.push(response.body.response._id); + }) + .then((response) => { + cy.createDetector(testDetector); + }); + }) .then(() => { // Go to the detectors table page From 27cb71aecde74e1a82d5da316933e9c904349baa Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 24 Apr 2023 11:34:49 +0200 Subject: [PATCH 48/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress.json | 1 + .../rule/sample_dns_field_mappings.json | 5 + cypress/integration/1_detectors.spec.js | 563 +++++++++--------- cypress/support/helpers.ts | 76 +++ 4 files changed, 348 insertions(+), 297 deletions(-) create mode 100644 cypress/fixtures/integration_tests/rule/sample_dns_field_mappings.json create mode 100644 cypress/support/helpers.ts diff --git a/cypress.json b/cypress.json index 48f248ec8..705b14add 100644 --- a/cypress.json +++ b/cypress.json @@ -5,6 +5,7 @@ "requestTimeout": 300000, "responseTimeout": 300000, "baseUrl": "http://localhost:5601", + "retries": 1, "env": { "opensearch_url": "localhost:9200", "opensearch_dashboards": "http://localhost:5601", diff --git a/cypress/fixtures/integration_tests/rule/sample_dns_field_mappings.json b/cypress/fixtures/integration_tests/rule/sample_dns_field_mappings.json new file mode 100644 index 000000000..b2f9b698e --- /dev/null +++ b/cypress/fixtures/integration_tests/rule/sample_dns_field_mappings.json @@ -0,0 +1,5 @@ +{ + "dns-question-registered_domain": "dns.question.registered_domain", + "dns-question-name": "dns.question.name", + "dns-answers-type": "dns.answers.type" +} diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 968b19062..322a19a41 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -8,125 +8,190 @@ import sample_windows_index_settings from '../fixtures/sample_windows_index_sett import sample_dns_index_settings from '../fixtures/sample_dns_index_settings.json'; import dns_name_rule_data from '../fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json'; import dns_type_rule_data from '../fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json'; +import dns_mapping_fields from '../fixtures/integration_tests/rule/sample_dns_field_mappings.json'; +import { + getInputByLabel, + getElementByTestSubject, + getElementByText, + getInputByPlaceholder, + getRadioButtonById, + selectComboboxItem, + validateDetailsItem, + urlShouldContain, + pressEnterKey, + validateTable, + clearCombobox, + getButtonByText, + getTextareaByLabel, +} from '../support/helpers'; const cypressIndexDns = 'cypress-index-dns'; const cypressIndexWindows = 'cypress-index-windows'; const detectorName = 'test detector'; -const testMappings = { - properties: { - 'dns-question-name': { - type: 'alias', - path: 'dns.question_name', - }, - }, -}; - const cypressDNSRule = dns_name_rule_data.title; -// const cypressDNSTypeRule = dns_type_rule_data.title; -const getNameField = () => cy.get(`input[placeholder="Enter a name for the detector."]`); -const getDataSourceField = () => cy.get(`.euiComboBox__input input`); -const getNextButton = () => cy.get('.euiButton').filter(':contains("Next")'); +const getNameField = () => getInputByPlaceholder('Enter a name for the detector.'); -const createDetector = (detectorName, dataSource, expectFailure) => { - // Locate Create detector button click to start - cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true }); +const getNextButton = () => getButtonByText('Next'); - // Check to ensure process started - cy.waitForPageLoad('create-detector', { - contains: 'Define detector', - }); +const getCreateDetectorButton = () => getButtonByText('Create detector'); - // Enter a name for the detector in the appropriate input - cy.get(`input[placeholder="Enter a name for the detector."]`).focus().type(detectorName); +const selectDnsLogType = () => getRadioButtonById('dns').click({ force: true }); - // Select our pre-seeded data source (check cypressIndexDns) - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type(`${dataSource}{enter}`); +const validateAlertPanel = (alertName) => + getElementByText('.euiTitle', 'Alert triggers') + .parentsUntil('.euiPanel') + .siblings() + .eq(2) + .within(() => getElementByText('button', alertName)); - cy.intercept({ - pathname: '/_plugins/_security_analytics/rules/_search', - query: { - prePackaged: 'true', - }, - }).as('getSigmaRules'); +const dataSourceLabel = 'Select or input source indexes or index patterns'; - // Select threat detector type (Windows logs) - cy.get(`input[id="dns"]`).click({ force: true }); +const getDataSourceField = () => getInputByLabel(dataSourceLabel); - cy.wait('@getSigmaRules').then(() => { - // Open Detection rules accordion - cy.get('[data-test-subj="detection-rules-btn"]').click({ force: true, timeout: 5000 }); +const openDetectorDetails = (detectorName) => { + getInputByPlaceholder('Search threat detectors') + .type(`${detectorName}`) + .then(() => pressEnterKey()); + getElementByText('.euiTableCellContent button', detectorName).click(); +}; - cy.contains('table tr', 'DNS', { - timeout: 120000, - }); +const editDetectorDetails = (detectorName, panelTitle) => { + urlShouldContain('detector-details').then(() => { + getElementByText('.euiTitle', detectorName); + getElementByText('.euiPanel .euiTitle', panelTitle) + .parent() + .siblings() + .find('button') + .contains('Edit') + .click(); }); +}; - // Check that correct page now showing - cy.contains('Configure field mapping - optional'); - cy.get('[aria-controls="mappedTitleFieldsAccordion"]').then(($btn) => { - if ($btn && $btn[0] && $btn[0].getAttribute('aria-expanded') === 'false') { - $btn[0].click(); - } +const validateEditFieldMappingsPanel = (length, mappings) => + cy.get('.editFieldMappings').within(() => { + cy.get('.euiAccordion__triggerWrapper button').then(($btn) => { + cy.get($btn).contains(`Automatically mapped fields (${length})`); + + // first check if the accordion is expanded, if not than expand the accordion + if ($btn[0].getAttribute('aria-expanded') === 'false') { + cy.get($btn[0]) + .click() + .then(() => { + cy.get('.euiAccordion__childWrapper .euiBasicTable').then(($table) => { + validateTable($table, length, mappings); + }); + }); + } + }); }); - // Click Next button to continue - cy.get('button').contains('Next').click({ force: true }); - - // Check that correct page now showing - cy.contains('Set up alert triggers'); - - // Type name of new trigger - cy.get(`input[placeholder="Enter a name to describe the alert condition"]`) - .focus() - .type('test_trigger'); +const createDetector = (detectorName, dataSource, expectFailure) => { + getCreateDetectorButton().click({ force: true }); - // Type in (or select) tags for the alert condition - cy.get(`[data-test-subj="alert-tags-combo-box"]`).type(`attack.defense_evasion{enter}`); + // TEST DETAILS PAGE + getNameField().type(detectorName); + selectComboboxItem(getDataSourceField(), dataSource); - // Select applicable severity levels - cy.get(`[data-test-subj="security-levels-combo-box"]`).click({ force: true }); - cy.contains('1 (Highest)').click({ force: true }); + selectDnsLogType(); - // Continue to next page - cy.contains('Next').click({ force: true }); + getElementByText('.euiAccordion .euiTitle', 'Detection rules (14 selected)') + .click({ force: true, timeout: 5000 }) + .then(() => cy.contains('.euiTable .euiTableRow', 'Dns')); - // Confirm page is reached - cy.contains('Review and create'); + getElementByText('.euiAccordion .euiTitle', 'Configure field mapping - optional'); + cy.get('[aria-controls="mappedTitleFieldsAccordion"]').then(($btn) => { + // first check if the accordion is expanded, if not than expand the accordion + if ($btn && $btn[0] && $btn[0].getAttribute('aria-expanded') === 'false') { + $btn[0].click(); + } + }); - // Confirm field mappings registered - cy.contains('Field mapping'); + // go to the alerts page + getNextButton().click({ force: true }); + + // TEST ALERTS PAGE + getElementByText('.euiTitle', 'Set up alert triggers'); + getInputByPlaceholder('Enter a name to describe the alert condition').type('test_trigger'); + getElementByTestSubject('alert-tags-combo-box') + .type(`attack.defense_evasion{enter}`) + .find('input') + .blur(); + + getElementByTestSubject('security-levels-combo-box').click(); + getElementByText('.euiComboBoxOption__content', '1 (Highest)').click({ force: true }); + + // go to review page + getNextButton().click({ force: true }); + + // TEST REVIEW AND CREATE PAGE + getElementByText('.euiTitle', 'Review and create'); + getElementByText('.euiTitle', 'Detector details'); + getElementByText('.euiTitle', 'Field mapping'); + getElementByText('.euiTitle', 'Alert triggers'); + + validateDetailsItem('Detector name', detectorName); + validateDetailsItem('Description', '-'); + validateDetailsItem('Detector schedule', 'Every 1 minute'); + validateDetailsItem('Detection rules', '14'); + validateDetailsItem('Created at', '-'); + validateDetailsItem('Last updated time', '-'); + validateDetailsItem('Detector dashboard', 'Not available for this log type'); - // Confirm entries user has made - cy.contains('Detector details'); - cy.contains(detectorName); - cy.contains('dns'); - cy.contains('test_trigger'); + if (!expectFailure) { + getElementByText('.euiTitle', 'Field mapping') + .parentsUntil('.euiPanel') + .siblings() + .eq(2) + .then(($el) => validateTable($el, 3, dns_mapping_fields)); + } - // Create the detector - cy.get('button').contains('Create').click({ force: true }); + validateAlertPanel('test_trigger'); - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); + cy.intercept('POST', '/mappings').as('createMappingsRequest'); + cy.intercept('POST', '/detectors').as('createDetectorRequest'); - cy.contains('Attempting to create the detector.'); + // create the detector + getElementByText('button', 'Create').click({ force: true }); - // Confirm detector active - cy.contains(detectorName); - cy.contains('Active'); + // TEST DETECTOR DETAILS PAGE + cy.wait('@createMappingsRequest'); if (!expectFailure) { - cy.contains('Actions'); + cy.wait('@createDetectorRequest').then((interceptor) => { + const detectorId = interceptor.response.body.response._id; + + cy.url() + .should('contain', detectorId) + .then(() => { + getElementByText('.euiCallOut', `Detector created successfully: ${detectorName}`); + + // Confirm detector state + getElementByText('.euiTitle', detectorName); + getElementByText('.euiHealth', 'Active').then(() => { + validateDetailsItem('Detector name', detectorName); + validateDetailsItem('Description', '-'); + validateDetailsItem('Detector schedule', 'Every 1 minute'); + validateDetailsItem('Detection rules', '14'); + validateDetailsItem('Detector dashboard', 'Not available for this log type'); + + cy.wait(5000); // waiting for the page to be reloaded after pushing detector id into route + getElementByText('button.euiTab', 'Alert triggers').should('be.visible').click(); + validateAlertPanel('test_trigger'); + + getElementByText('button.euiTab', 'Field mappings').should('be.visible').click(); + if (!expectFailure) { + getElementByText('.euiTitle', 'Field mapping') + .parentsUntil('.euiPanel') + .siblings() + .eq(2) + .then(($el) => validateTable($el, 3, dns_mapping_fields)); + } + }); + }); + }); } - - cy.contains('Detector configuration'); - cy.contains('Field mappings'); - cy.contains('Alert triggers'); - cy.contains('Detector details'); - cy.contains('Created at'); - cy.contains('Last updated time'); }; describe('Detectors', () => { @@ -157,70 +222,61 @@ describe('Detectors', () => { beforeEach(() => { cy.intercept('/detectors/_search').as('detectorsSearch'); + // Visit Detectors page cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete'); - - // Check that correct page is showing - cy.waitForPageLoad('detectors', { - contains: 'Threat detectors', - }); }); it('...should validate form', () => { - // Locate Create detector button click to start - cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true }); - - // Check to ensure process started - cy.waitForPageLoad('create-detector', { - contains: 'Define detector', - }); + getCreateDetectorButton().click({ force: true }); getNextButton().should('be.disabled'); getNameField().should('be.empty'); getNameField().type('text').blur(); - cy.get('.euiFormErrorText').contains( - 'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.' - ); + getNameField() + .parentsUntil('.euiFormRow__fieldWrapper') + .siblings() + .contains( + 'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.' + ); - getNameField().type(' and more text').blur(); - cy.get('.euiFormErrorText').should('not.exist'); + getNameField() + .type(' and more text') + .blur() + .parentsUntil('.euiFormRow__fieldWrapper') + .siblings() + .should('not.exist'); getNextButton().should('be.disabled'); - getDataSourceField().focus().blur(); - cy.get('.euiFormErrorText').contains('Select an input source'); + getDataSourceField() + .focus() + .blur() + .parentsUntil('.euiFormRow__fieldWrapper') + .siblings() + .contains('Select an input source'); getNextButton().should('be.disabled'); - getDataSourceField().type(`${cypressIndexDns}{enter}`); - cy.get('.euiFormErrorText').should('not.exist'); + selectComboboxItem(getDataSourceField(), cypressIndexDns); + getDataSourceField() + .blur() + .parentsUntil('.euiFormRow__fieldWrapper') + .find('.euiFormErrorText') + .should('not.exist'); getNextButton().should('not.be.disabled'); }); it('...should show mappings warning', () => { - // Locate Create detector button click to start - cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true }); - - // Check to ensure process started - cy.waitForPageLoad('create-detector', { - contains: 'Define detector', - }); + getCreateDetectorButton().click({ force: true }); - // Select our pre-seeded data source (check cypressIndexDns) + selectComboboxItem(getDataSourceField(), cypressIndexDns); - // cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true }); - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - `${cypressIndexDns}{enter}` - ); - - // Select threat detector type (Windows logs) - cy.get(`input[id="dns"]`).click({ force: true }); + selectDnsLogType(); - // Select our pre-seeded data source (check cypressIndexDns) - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - `${cypressIndexWindows}{enter}` - ); + selectComboboxItem(getDataSourceField(), cypressIndexWindows); + getDataSourceField().blur(); cy.get('.euiCallOut') .should('be.visible') @@ -231,208 +287,113 @@ describe('Detectors', () => { it('...can be created', () => { createDetector(detectorName, cypressIndexDns, false); - cy.contains('Detector created successfully'); + getElementByText('.euiCallOut', 'Detector created successfully'); }); it('...can fail creation', () => { createDetector(`${detectorName}_fail`, '.kibana_1', true); - cy.contains('Create detector failed.'); + getElementByText('.euiCallOut', 'Create detector failed.'); }); it('...basic details can be edited', () => { - // Click on detector name - cy.contains(detectorName).click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); + openDetectorDetails(detectorName); - // Click "Edit" button in detector details - cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); + editDetectorDetails(detectorName, 'Detector details'); - // Confirm arrival at "Edit detector details" page - cy.waitForPageLoad('edit-detector-details', { - contains: 'Edit detector details', + urlShouldContain('edit-detector-details').then(() => { + getElementByText('.euiTitle', 'Edit detector details'); }); - // Change detector name - cy.get(`input[placeholder="Enter a name for the detector."]`) - .type('{selectall}{backspace}') - .type('test detector edited'); + getNameField().type('{selectall}{backspace}').type('test detector edited'); + getTextareaByLabel('Description - optional').type('Edited description'); - // Change detector description - cy.get(`[data-test-subj="define-detector-detector-description"]`) - .focus() - .type('Edited description'); + clearCombobox(getDataSourceField()); + selectComboboxItem(getDataSourceField(), cypressIndexWindows); - // Change input source - cy.get( - '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' - ).click({ force: true }); - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - `${cypressIndexWindows}{enter}` - ); + getInputByLabel('Run every').type('{selectall}{backspace}').type('10'); + getInputByLabel('Run every', 'select').select('Hours'); - // Change detector scheduling - cy.get(`[data-test-subj="detector-schedule-number-select"]`) - .focus() - .type('{selectall}{backspace}') - .type('10'); - cy.get(`[data-test-subj="detector-schedule-unit-select"]`).select('Hours'); - - // Save changes to detector details - cy.get(`[data-test-subj="save-basic-details-edits"]`).click({ force: true }); - - // Confirm taken to detector details page - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); - - // Verify edits are applied - cy.contains('test detector edited'); - cy.contains('Every 10 hours'); - cy.contains('Edited description'); - cy.contains(cypressIndexWindows); - - // Change input source - cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); - cy.get( - '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' - ).click({ force: true }); - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - `${cypressIndexDns}{enter}` - ); - cy.get(`[data-test-subj="save-basic-details-edits"]`).click({ force: true }); - cy.contains(cypressIndexDns); - }); - - it('...rules can be edited', () => { - // Ensure start on main detectors page - cy.waitForPageLoad('detectors', { - contains: 'Threat detectors', - }); - - // Click on detector name - cy.contains(detectorName).click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); - - // Confirm number of rules before edit - cy.contains('Active rules (14)'); - - // Click "Edit" button in Detector rules panel - cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); - - // Confirm arrival on "Edit detector rules" page - cy.waitForPageLoad('edit-detector-rules', { - contains: 'Edit detector rules', - }); - - // Search for specific rule - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - - // Toggle single search result to unchecked - cy.contains('table tr', cypressDNSRule).within(() => { - // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case. - cy.wait(1000); - cy.get('button').eq(1).click(); - }); - - // Save changes - cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ force: true }); + getElementByText('button', 'Save changes').click({ force: true }); - // Confirm 1 rule has been removed from detector - cy.contains('Active rules (13)'); + urlShouldContain('detector-details').then(() => { + validateDetailsItem('Detector name', 'test detector edited'); + validateDetailsItem('Description', 'Edited description'); + validateDetailsItem('Detector schedule', 'Every 10 hours'); + validateDetailsItem('Data source', cypressIndexWindows); - // Click "Edit" button in Detector rules panel - cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); + editDetectorDetails(detectorName, 'Detector details'); - // Confirm arrival on "Edit detector rules" page - cy.waitForPageLoad('edit-detector-rules', { - contains: 'Edit detector rules', - }); + urlShouldContain('edit-detector-details').then(() => { + getElementByText('.euiTitle', 'Edit detector details'); + }); - // Search for specific rule - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); + clearCombobox(getDataSourceField()); + selectComboboxItem(getDataSourceField(), cypressIndexDns); - // Toggle single search result to checked - cy.contains('table tr', cypressDNSRule).within(() => { - cy.wait(2000); - cy.get('button').eq(1).click({ force: true }); + getElementByText('button', 'Save changes').click({ force: true }); + urlShouldContain('detector-details').then(() => { + validateDetailsItem('Data source', cypressIndexDns); + }); }); + }); - // Save changes - cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: detectorName, + it('...rules can be edited', () => { + openDetectorDetails(detectorName); + + editDetectorDetails(detectorName, 'Active rules'); + getElementByText('.euiTitle', 'Detection rules (14)'); + + getInputByPlaceholder('Search...') + .type(`${cypressDNSRule}`) + .then(() => pressEnterKey()); + + getElementByText('.euiTableCellContent button', cypressDNSRule) + .parents('td') + .prev() + .find('.euiTableCellContent button') + .click(); + + getElementByText('.euiTitle', 'Detection rules (13)'); + getElementByText('button', 'Save changes').click({ force: true }); + urlShouldContain('detector-details').then(() => { + getElementByText('.euiTitle', detectorName); + getElementByText('.euiPanel .euiTitle', 'Active rules (13)'); }); - - // Confirm 1 rule has been added to detector - cy.contains('Active rules (14)'); }); it('...should update field mappings if data source is changed', () => { - // Click on detector name - cy.contains(detectorName).click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); + openDetectorDetails(detectorName); - // Click "Edit" button in detector details - cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); + editDetectorDetails(detectorName, 'Detector details'); - // Confirm arrival at "Edit detector details" page - cy.waitForPageLoad('edit-detector-details', { - contains: 'Edit detector details', + urlShouldContain('edit-detector-details').then(() => { + getElementByText('.euiTitle', 'Edit detector details'); }); cy.get('.reviewFieldMappings').should('not.exist'); - // Change input source - cy.get( - '.euiFormControlLayoutIcons > .euiFormControlLayoutClearButton > .euiIcon > path' - ).click({ force: true }); - cy.get(`[data-test-subj="define-detector-select-data-source"]`) - .find('input') - .should('not.have.value'); - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - `${cypressIndexDns}{enter}` - ); + clearCombobox(getDataSourceField()); + getDataSourceField().should('not.have.value'); + getDataSourceField().type(`${cypressIndexDns}{enter}`); cy.get('.reviewFieldMappings').should('be.visible'); - cy.contains('Automatically mapped fields (3)'); + validateEditFieldMappingsPanel(3, dns_mapping_fields); }); it('...should show field mappings if rule selection is changed', () => { - cy.intercept('POST', 'rules/_search?prePackaged=true', { - delay: 5000, - }).as('getPrePackagedRules'); - - cy.intercept('POST', 'rules/_search?prePackaged=false', { - delay: 5000, - }).as('getCustomRules'); - cy.intercept('mappings/view').as('getMappingsView'); - // Click on detector name - cy.contains(detectorName).click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: detectorName, - }); + openDetectorDetails(detectorName); - // Click "Edit" button in detector details - cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); + editDetectorDetails(detectorName, 'Active rules'); - // Confirm arrival at "Edit detector details" page - cy.waitForPageLoad('edit-detector-rules', { - contains: 'Edit detector rules', + urlShouldContain('edit-detector-rules').then(() => { + getElementByText('.euiTitle', 'Edit detector rules'); }); cy.get('.reviewFieldMappings').should('not.exist'); cy.wait('@detectorsSearch'); - cy.wait('@getCustomRules'); - cy.wait('@getPrePackagedRules'); // Toggle single search result to unchecked cy.get( @@ -441,23 +402,31 @@ describe('Detectors', () => { cy.wait('@getMappingsView'); cy.get('.reviewFieldMappings').should('be.visible'); + validateEditFieldMappingsPanel(3, dns_mapping_fields); }); it('...can be deleted', () => { - // Click on detector to be removed - cy.contains('test detector edited').click({ force: true }); - - // Confirm page - cy.waitForPageLoad('detector-details', { - contains: 'Detector details', - }); - - // Click "Actions" button, the click "Delete" - cy.get('button').contains('Actions').click({ force: true }); - cy.get('button').contains('Delete').click({ force: true }); + cy.intercept('/_plugins/_security_analytics/rules/_search?prePackaged=true').as( + 'getSigmaRules' + ); + cy.intercept('/_plugins/_security_analytics/rules/_search?prePackaged=false').as( + 'getCustomRules' + ); + openDetectorDetails(detectorName); - // Confirm detector is deleted - cy.contains('There are no existing detectors'); + cy.wait('@detectorsSearch'); + cy.wait('@getCustomRules'); + cy.wait('@getSigmaRules'); + + getButtonByText('Actions') + .click({ force: true }) + .then(() => { + cy.intercept('/detectors').as('detectors'); + getElementByText('.euiContextMenuItem', 'Delete').click({ force: true }); + cy.wait('@detectors').then(() => { + cy.contains('There are no existing detectors'); + }); + }); }); after(() => cy.cleanUpTests()); diff --git a/cypress/support/helpers.ts b/cypress/support/helpers.ts new file mode 100644 index 000000000..138d62f93 --- /dev/null +++ b/cypress/support/helpers.ts @@ -0,0 +1,76 @@ +export const getElementByText = (locator: string, text: string) => + locator + ? cy.get(locator).filter(`:contains("${text}")`).should('be.visible') + : cy.contains(text).should('be.visible'); + +export const getButtonByText = (text: string) => getElementByText('.euiButton', text); + +export const getInputByPlaceholder = (placeholder: string) => + cy.get(`input[placeholder="${placeholder}"]`); + +export const getComboboxByPlaceholder = (placeholder: string) => + getElementByText('.euiComboBoxPlaceholder', placeholder) + .siblings('.euiComboBox__input') + .find('input'); + +export const getInputByLabel = (label: string, type = 'input') => + getElementByText('.euiFormRow__labelWrapper', label).siblings().find(type); + +export const getTextareaByLabel = (label: string) => getInputByLabel(label, 'textarea'); + +export const getElementByTestSubject = (subject: string) => cy.get(`[data-test-subj="${subject}"]`); + +export const getRadioButtonById = (id: string) => cy.get(`input[id="${id}"]`); + +export const selectComboboxItem = (combo: any, items: string | string[]) => { + combo + .focus() + .click({ force: true }) + .then(() => { + if (typeof items === 'string') { + items = [items]; + } + items.map((item) => + cy.get('.euiComboBoxOptionsList__rowWrap').find('button').contains(item).click() + ); + }); +}; + +export const clearCombobox = (combo: any) => + combo.parentsUntil('.euiComboBox__inputWrap').siblings().find('button').eq(0).click(); + +export const validateDetailsItem = (label: string, value: string) => { + getElementByText('.euiFlexItem label', label).parent().siblings().contains(value); +}; + +export const urlShouldContain = (path: string) => cy.url().should('contain', `#/${path}`); + +export const pressEnterKey = () => + Cypress.automation('remote:debugger:protocol', { + command: 'Input.dispatchKeyEvent', + params: { + type: 'char', + unmodifiedText: '\r', + text: '\r', + }, + }); + +export const validateTable = ( + container: any, // jqueryElement + length: number, + dataMap: { [key: string]: string } +) => { + cy.get(container) + .should('be.visible') + .find('table tbody') + .find('tr') + .should('have.length', length) + .within(($tr: any) => { + if (dataMap) { + for (let logField in dataMap) { + cy.get($tr).find('td').contains(logField); + cy.get($tr).find('td').contains(dataMap[logField]); + } + } + }); +}; From 03ffd6818ed19dc28602b3b7a2a9d5d92806f5c1 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 24 Apr 2023 16:24:14 +0200 Subject: [PATCH 49/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- .../detector/create_dns_detector_data.json | 10 +- .../detector/create_usb_detector_data.json | 6 +- .../create_dns_rule_with_name_selection.json | 4 +- .../create_dns_rule_with_type_selection.json | 4 +- .../rule/create_network_rule.json | 4 +- .../rule/create_windows_usb_rule.json | 4 +- cypress/fixtures/sample_detector.json | 6 +- cypress/integration/3_alerts.spec.js | 158 +++++------------- cypress/integration/4_findings.spec.js | 107 ++++-------- cypress/support/helpers.ts | 80 +++++++++ 10 files changed, 178 insertions(+), 205 deletions(-) diff --git a/cypress/fixtures/integration_tests/detector/create_dns_detector_data.json b/cypress/fixtures/integration_tests/detector/create_dns_detector_data.json index 276c56db2..e2f5447b8 100644 --- a/cypress/fixtures/integration_tests/detector/create_dns_detector_data.json +++ b/cypress/fixtures/integration_tests/detector/create_dns_detector_data.json @@ -27,19 +27,19 @@ "triggers": [ { "name": "DNS name alert", - "sev_levels": ["low"], - "tags": ["dns.low"], + "sev_levels": ["high"], + "tags": ["dns.high"], "actions": [ { "id": "", - "name": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: Cypress DNS Detector", + "name": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: Cypress DNS Detector", "destination_id": "", "subject_template": { - "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: Cypress DNS Detector", + "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: Cypress DNS Detector", "lang": "mustache" }, "message_template": { - "source": "Triggered alert condition: \nSeverity: 1 (Highest)\nThreat detector: Cypress DNS Detector\nDescription: Detects DNS names.\nDetector data sources:\n\tdns", + "source": "Triggered alert condition: \nSeverity: 1 (Highest) \nThreat detector: Cypress DNS Detector\nDescription: Detects DNS names.\nDetector data sources:\n\tdns", "lang": "mustache" }, "throttle_enabled": false, diff --git a/cypress/fixtures/integration_tests/detector/create_usb_detector_data.json b/cypress/fixtures/integration_tests/detector/create_usb_detector_data.json index 07392d280..b68c08406 100644 --- a/cypress/fixtures/integration_tests/detector/create_usb_detector_data.json +++ b/cypress/fixtures/integration_tests/detector/create_usb_detector_data.json @@ -27,7 +27,7 @@ "triggers": [ { "name": "USB plugged in alert", - "sev_levels": ["low"], + "sev_levels": ["high"], "tags": ["windows.usb"], "actions": [ { @@ -35,11 +35,11 @@ "name": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: USB Detector", "destination_id": "", "subject_template": { - "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: USB Detector", + "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: USB Detector", "lang": "mustache" }, "message_template": { - "source": "Triggered alert condition: \nSeverity: 1 (Highest)\nThreat detector: USB Detector\nDescription: Detect USB plugged in.\nDetector data sources:\n\twindows", + "source": "Triggered alert condition: \nSeverity: 1 (Highest) \nThreat detector: USB Detector\nDescription: Detect USB plugged in.\nDetector data sources:\n\twindows", "lang": "mustache" }, "throttle_enabled": false, diff --git a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json index 98e8573d1..7c1e7c8fb 100644 --- a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json +++ b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json @@ -12,12 +12,12 @@ ], "tags": [ { - "value": "dns.low" + "value": "dns.high" } ], "log_source": "", "detection": "selection:\n dns-question-name:\n - QuestionName\ncondition: selection", - "level": "low", + "level": "high", "false_positives": [ { "value": "" diff --git a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json index a7335e830..e447a30d5 100644 --- a/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json +++ b/cypress/fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json @@ -12,12 +12,12 @@ ], "tags": [ { - "value": "dns.low" + "value": "dns.high" } ], "log_source": "", "detection": "selection:\n dns-answers-type:\n - AnswerType\ncondition: selection", - "level": "low", + "level": "high", "false_positives": [ { "value": "" diff --git a/cypress/fixtures/integration_tests/rule/create_network_rule.json b/cypress/fixtures/integration_tests/rule/create_network_rule.json index 43e69cff4..2937fc79d 100644 --- a/cypress/fixtures/integration_tests/rule/create_network_rule.json +++ b/cypress/fixtures/integration_tests/rule/create_network_rule.json @@ -12,12 +12,12 @@ ], "tags": [ { - "value": "network.low" + "value": "network.high" } ], "log_source": "", "detection": "selection:\n keywords:\n - erase\n - delete\n - YXC\ncondition: selection", - "level": "low", + "level": "high", "false_positives": [ { "value": "" diff --git a/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json b/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json index 3db4cf141..fb14944c6 100644 --- a/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json +++ b/cypress/fixtures/integration_tests/rule/create_windows_usb_rule.json @@ -16,8 +16,8 @@ } ], "log_source": "", - "detection": "selection:\n winlog.event_id:\n - 2003\n - 2100\n - 2102\ncondition: selection", - "level": "low", + "detection": "selection:\n winlog-event_id:\n - 2003\n - 2100\n - 2102\ncondition: selection", + "level": "high", "false_positives": [ { "value": "" diff --git a/cypress/fixtures/sample_detector.json b/cypress/fixtures/sample_detector.json index 7c92db523..a17853598 100644 --- a/cypress/fixtures/sample_detector.json +++ b/cypress/fixtures/sample_detector.json @@ -31,7 +31,7 @@ "triggers": [ { "name": "sample_alert_condition", - "sev_levels": ["low"], + "sev_levels": ["high"], "tags": [], "actions": [ { @@ -39,11 +39,11 @@ "name": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: sample_detector", "destination_id": "", "subject_template": { - "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: sample_detector", + "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: sample_detector", "lang": "mustache" }, "message_template": { - "source": "Triggered alert condition: \nSeverity: 1 (Highest)\nThreat detector: sample_detector\nDescription: Description for sample_detector.\nDetector data sources:\n\twindows", + "source": "Triggered alert condition: \nSeverity: 1 (Highest) \nThreat detector: sample_detector\nDescription: Description for sample_detector.\nDetector data sources:\n\twindows", "lang": "mustache" }, "throttle_enabled": false, diff --git a/cypress/integration/3_alerts.spec.js b/cypress/integration/3_alerts.spec.js index 1b7ef92f0..9d3403491 100644 --- a/cypress/integration/3_alerts.spec.js +++ b/cypress/integration/3_alerts.spec.js @@ -5,88 +5,32 @@ import moment from 'moment'; import { DETECTOR_TRIGGER_TIMEOUT, OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; -import sample_index_settings from '../fixtures/sample_windows_index_settings.json'; -import sample_alias_mappings from '../fixtures/sample_alias_mappings.json'; -import sample_detector from '../fixtures/integration_tests/detector/create_usb_detector_data.json'; -import sample_document from '../fixtures/sample_document.json'; -import windows_rule_data from '../fixtures/integration_tests/rule/create_windows_usb_rule.json'; - -const testIndex = 'sample_alerts_spec_cypress_test_index'; -const testDetectorName = 'alerts_spec_cypress_test_detector'; -const testDetectorAlertCondition = `${testDetectorName} alert condition`; - -// Creating a unique detector JSON for this test spec -const testDetector = { - ...sample_detector, - name: testDetectorName, - inputs: [ - { - detector_input: { - ...sample_detector.inputs[0].detector_input, - description: `Description for ${testDetectorName}`, - indices: [testIndex], - }, - }, - ], - triggers: [ - { - ...sample_detector.triggers[0], - name: testDetectorAlertCondition, - }, - ], -}; - -// The exact minutes/seconds for the start and last updated time will be difficult to predict, -// but all of the alert time fields should all contain the date in this format. -const date = moment(moment.now()).format('MM/DD/YY'); +import indexSettings from '../fixtures/sample_windows_index_settings.json'; +import aliasMappings from '../fixtures/sample_alias_mappings.json'; +import indexDoc from '../fixtures/sample_document.json'; +import ruleSettings from '../fixtures/integration_tests/rule/create_windows_usb_rule.json'; +import { createDetector } from '../support/helpers'; + +const indexName = 'test-index'; +const detectorName = 'test-detector'; +const alertName = `${detectorName} alert condition`; +const date = moment(moment.now()).format('MM/DD/YY'); const docCount = 4; + +let testDetector; describe('Alerts', () => { before(() => { - // Delete any pre-existing test detectors - cy.cleanUpTests() - // Create test index - .then(() => cy.createIndex(testIndex, sample_index_settings)) - - // Create field mappings - .then(() => - cy.createAliasMappings(testIndex, testDetector.detector_type, sample_alias_mappings, true) - ) - - // Create test detector - .then(() => { - cy.createRule(windows_rule_data) - .then((response) => { - testDetector.inputs[0].detector_input.custom_rules[0].id = response.body.response._id; - testDetector.triggers[0].ids.push(response.body.response._id); - }) - .then((response) => { - cy.createDetector(testDetector); - }); - }) - - .then(() => { - // Go to the detectors table page - cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); - - // Check that correct page is showing - cy.waitForPageLoad('detectors', { - contains: 'Threat detectors', - }); - - // Filter table to only show the test detector - cy.get(`input[type="search"]`).type(`${testDetector.name}{enter}`); - - // Confirm detector was created - cy.get('tbody > tr').should(($tr) => { - expect($tr, 'detector name').to.contain(testDetector.name); - }); - }); - - // Ingest documents to the test index - for (let i = 0; i < docCount; i++) { - cy.insertDocumentToIndex(testIndex, '', sample_document); - } + const subject = createDetector( + detectorName, + indexName, + indexSettings, + aliasMappings, + ruleSettings, + indexDoc, + 4 + ); + testDetector = subject.detector; // Wait for the detector to execute cy.wait(DETECTOR_TRIGGER_TIMEOUT); @@ -119,9 +63,7 @@ describe('Alerts', () => { cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click({ force: true }); // Confirm there are alerts created - cy.get('tbody > tr') - .filter(`:contains(${testDetectorAlertCondition})`) - .should('have.length', docCount); + cy.get('tbody > tr').filter(`:contains(${alertName})`).should('have.length', docCount); }); it('contain expected values in table', () => { @@ -131,7 +73,7 @@ describe('Alerts', () => { expect($tr, 'trigger name').to.contain(testDetector.triggers[0].name); expect($tr, 'detector name').to.contain(testDetector.name); expect($tr, 'status').to.contain('Active'); - expect($tr, 'severity').to.contain('4 (Low)'); + expect($tr, 'severity').to.contain('1 (Highest)'); }); }); @@ -154,7 +96,9 @@ describe('Alerts', () => { cy.get('[data-test-subj="text-details-group-content-alert-status"]').contains('Active'); // Confirm alert severity - cy.get('[data-test-subj="text-details-group-content-alert-severity"]').contains('4 (Low)'); + cy.get('[data-test-subj="text-details-group-content-alert-severity"]').contains( + '1 (Highest)' + ); // Confirm alert start time is present cy.get('[data-test-subj="text-details-group-content-start-time"]').contains(date); @@ -167,12 +111,12 @@ describe('Alerts', () => { // Wait for the findings table to finish loading cy.contains('Findings (1)'); - cy.contains('USB Device Plugged'); + cy.contains('Cypress USB Rule'); // Confirm alert findings contain expected values cy.get('tbody > tr').should(($tr) => { expect($tr, `timestamp`).to.contain(date); - expect($tr, `rule name`).to.contain('USB Device Plugged'); + expect($tr, `rule name`).to.contain('Cypress USB Rule'); expect($tr, `detector name`).to.contain(testDetector.name); expect($tr, `log type`).to.contain('Windows'); }); @@ -196,7 +140,7 @@ describe('Alerts', () => { cy.get('[data-test-subj="alert-details-flyout"]').within(() => { // Wait for findings table to finish loading - cy.contains('USB Device Plugged'); + cy.contains('Cypress USB Rule'); // Click the details button for the first finding cy.get('tbody > tr') @@ -228,32 +172,32 @@ describe('Alerts', () => { cy.get('[data-test-subj="finding-details-flyout-rule-accordion-0"]').within(() => { // Confirm the accordion button contains the expected name cy.get('[data-test-subj="finding-details-flyout-rule-accordion-button"]').contains( - 'USB Device Plugged' + 'Cypress USB Rule' ); // Confirm the accordion button contains the expected severity cy.get('[data-test-subj="finding-details-flyout-rule-accordion-button"]').contains( - 'Severity: Low' + 'Severity: High' ); // Confirm the rule name - cy.get('[data-test-subj="finding-details-flyout-USB Device Plugged-details"]').contains( - 'USB Device Plugged' + cy.get('[data-test-subj="finding-details-flyout-Cypress USB Rule-details"]').contains( + 'Cypress USB Rule' ); // Confirm the rule severity - cy.get('[data-test-subj="finding-details-flyout-rule-severity"]').contains('Low'); + cy.get('[data-test-subj="finding-details-flyout-rule-severity"]').contains('High'); // Confirm the rule category cy.get('[data-test-subj="finding-details-flyout-rule-category"]').contains('Windows'); // Confirm the rule description cy.get('[data-test-subj="finding-details-flyout-rule-description"]').contains( - 'Detects plugged USB devices' + 'USB plugged-in rule' ); // Confirm the rule tags - ['low', 'windows', 'attack.initial_access', 'attack.t1200'].forEach((tag) => { + ['high', 'windows'].forEach((tag) => { cy.get('[data-test-subj="finding-details-flyout-rule-tags"]').contains(tag); }); }); @@ -264,19 +208,13 @@ describe('Alerts', () => { .then((text) => expect(text).to.not.equal('-')); // Confirm the rule index - cy.get('[data-test-subj="finding-details-flyout-rule-document-index"]').contains(testIndex); + cy.get('[data-test-subj="finding-details-flyout-rule-document-index"]').contains(indexName); // Confirm the rule document matches // The EuiCodeEditor used for this component stores each line of the JSON in an array of elements; // so this test formats the expected document into an array of strings, // and matches each entry with the corresponding element line. - const document = JSON.stringify( - JSON.parse( - '{"EventTime":"2020-02-04T14:59:39.343541+00:00","HostName":"EC2AMAZ-EPO7HKA","Keywords":"9223372036854775808","SeverityValue":2,"Severity":"INFO","EventID":2003,"SourceName":"Microsoft-Windows-Sysmon","ProviderGuid":"{5770385F-C22A-43E0-BF4C-06F5698FFBD9}","Version":5,"TaskValue":22,"OpcodeValue":0,"RecordNumber":9532,"ExecutionProcessID":1996,"ExecutionThreadID":2616,"Channel":"Microsoft-Windows-Sysmon/Operational","Domain":"NT AUTHORITY","AccountName":"SYSTEM","UserID":"S-1-5-18","AccountType":"User","Message":"Dns query:\\r\\nRuleName: \\r\\nUtcTime: 2020-02-04 14:59:38.349\\r\\nProcessGuid: {b3c285a4-3cda-5dc0-0000-001077270b00}\\r\\nProcessId: 1904\\r\\nQueryName: EC2AMAZ-EPO7HKA\\r\\nQueryStatus: 0\\r\\nQueryResults: 172.31.46.38;\\r\\nImage: C:\\\\Program Files\\\\nxlog\\\\nxlog.exe","Category":"Dns query (rule: DnsQuery)","Opcode":"Info","UtcTime":"2020-02-04 14:59:38.349","ProcessGuid":"{b3c285a4-3cda-5dc0-0000-001077270b00}","ProcessId":"1904","QueryName":"EC2AMAZ-EPO7HKA","QueryStatus":"0","QueryResults":"172.31.46.38;","Image":"C:\\\\Program Files\\\\nxlog\\\\regsvr32.exe","EventReceivedTime":"2020-02-04T14:59:40.780905+00:00","SourceModuleName":"in","SourceModuleType":"im_msvistalog","CommandLine":"eachtest","Initiated":"true","Provider_Name":"Microsoft-Windows-Kernel-General","TargetObject":"\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\Outlook\\\\Security","EventType":"SetValue"}' - ), - null, - 2 - ); + const document = JSON.stringify(JSON.parse('{"winlog.event_id": 2003}'), null, 2); const documentLines = document.split('\n'); cy.get('[data-test-subj="finding-details-flyout-rule-document"]') .get('[class="euiCodeBlock__line"]') @@ -340,7 +278,7 @@ describe('Alerts', () => { // Confirm there is an "Acknowledged" alert cy.get('tbody > tr').should(($tr) => { - expect($tr, `alert name`).to.contain(testDetectorAlertCondition); + expect($tr, `alert name`).to.contain(alertName); expect($tr, `status`).to.contain('Acknowledged'); }); @@ -352,7 +290,7 @@ describe('Alerts', () => { // Confirm there are now 2 "Acknowledged" alerts cy.get('tbody > tr') - .filter(`:contains(${testDetectorAlertCondition})`) + .filter(`:contains(${alertName})`) .should('contain', 'Active') .should('contain', 'Acknowledged'); }); @@ -364,9 +302,7 @@ describe('Alerts', () => { cy.contains('Active').click({ force: true }); }); - cy.get('tbody > tr') - .filter(`:contains(${testDetectorAlertCondition})`) - .should('have.length', 3); + cy.get('tbody > tr').filter(`:contains(${alertName})`).should('have.length', 3); cy.get('tbody > tr') // Click the "Acknowledge" icon button in the first row @@ -375,9 +311,7 @@ describe('Alerts', () => { cy.get('[aria-label="Acknowledge"]').click({ force: true }); }); - cy.get('tbody > tr') - .filter(`:contains(${testDetectorAlertCondition})`) - .should('have.length', 2); + cy.get('tbody > tr').filter(`:contains(${alertName})`).should('have.length', 2); // Filter the table to show only "Acknowledged" alerts cy.get('[data-text="Status"]'); @@ -387,9 +321,7 @@ describe('Alerts', () => { }); // Confirm there are now 3 "Acknowledged" alerts - cy.get('tbody > tr') - .filter(`:contains(${testDetectorAlertCondition})`) - .should('have.length', 2); + cy.get('tbody > tr').filter(`:contains(${alertName})`).should('have.length', 2); }); it('can be acknowledged via flyout button', () => { @@ -432,7 +364,7 @@ describe('Alerts', () => { cy.get('[data-test-subj="alert-details-flyout"]').within(() => { // Wait for findings table to finish loading - cy.contains('USB Device Plugged'); + cy.contains('Cypress USB Rule'); // Click the details button for the first finding cy.get('tbody > tr') diff --git a/cypress/integration/4_findings.spec.js b/cypress/integration/4_findings.spec.js index a490de9fa..eec8234b8 100644 --- a/cypress/integration/4_findings.spec.js +++ b/cypress/integration/4_findings.spec.js @@ -4,30 +4,33 @@ */ import { DETECTOR_TRIGGER_TIMEOUT, OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; -import sample_document from '../fixtures/sample_document.json'; -import sample_index_settings from '../fixtures/sample_windows_index_settings.json'; -import sample_field_mappings from '../fixtures/sample_field_mappings.json'; -import sample_detector from '../fixtures/sample_detector.json'; +import indexSettings from '../fixtures/sample_windows_index_settings.json'; +import aliasMappings from '../fixtures/sample_alias_mappings.json'; +import indexDoc from '../fixtures/sample_document.json'; +import ruleSettings from '../fixtures/integration_tests/rule/create_windows_usb_rule.json'; +import { createDetector } from '../support/helpers'; +const indexName = 'test-index'; +const detectorName = 'test-detector'; +const ruleName = 'Cypress USB Rule'; + +let testDetector; describe('Findings', () => { - const ruleTags = ['low', 'windows']; - const indexName = 'cypress-test-windows'; + const ruleTags = ['high', 'windows']; before(() => { - cy.cleanUpTests(); - - // Visit Findings page - cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/findings`); - - // create test index, mappings, and detector - cy.createIndex(indexName, sample_index_settings); - cy.createAliasMappings(indexName, 'windows', sample_field_mappings, true); - cy.createDetector(sample_detector); - - // Ingest a new document - cy.ingestDocument(indexName, sample_document); - - // wait for detector interval to pass + const subject = createDetector( + detectorName, + indexName, + indexSettings, + aliasMappings, + ruleSettings, + indexDoc, + 4 + ); + testDetector = subject.detector; + + // Wait for the detector to execute cy.wait(DETECTOR_TRIGGER_TIMEOUT); }); @@ -49,14 +52,13 @@ describe('Findings', () => { cy.contains('No items found').should('not.exist'); // Check for expected findings - cy.contains('sample_detector'); cy.contains('Windows'); - cy.contains('Low'); + cy.contains('High'); }); it('displays finding details flyout when user clicks on View details icon', () => { // filter table to show only sample_detector findings - cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector'); + cy.get(`input[placeholder="Search findings"]`).ospSearch(indexName); // Click View details icon cy.getTableFirstRow('[data-test-subj="view-details-icon"]').then(($el) => { @@ -73,7 +75,7 @@ describe('Findings', () => { it('displays finding details flyout when user clicks on Finding ID', () => { // filter table to show only sample_detector findings - cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector'); + cy.get(`input[placeholder="Search findings"]`).ospSearch(indexName); // Click findingId to trigger Finding details flyout cy.getTableFirstRow('[data-test-subj="finding-details-flyout-button"]').then(($el) => { @@ -92,7 +94,7 @@ describe('Findings', () => { // find a better way to test this dialog, condition is based on `indexPatternId` xit('displays finding details and create an index pattern from flyout', () => { // filter table to show only sample_detector findings - cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector'); + cy.get(`input[placeholder="Search findings"]`).ospSearch(indexName); // Click findingId to trigger Finding details flyout cy.getTableFirstRow('[data-test-subj="finding-details-flyout-button"]').then(($el) => { @@ -121,7 +123,7 @@ describe('Findings', () => { it('allows user to view details about rules that were triggered', () => { // filter table to show only sample_detector findings - cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector'); + cy.get(`input[placeholder="Search findings"]`).ospSearch(indexName); // open Finding details flyout via finding id link. cy.wait essential, timeout insufficient. cy.get(`[data-test-subj="view-details-icon"]`).eq(0).click({ force: true }); @@ -132,10 +134,9 @@ describe('Findings', () => { // Confirm content cy.contains('Documents'); - cy.contains('Detects plugged USB devices'); - cy.contains('Low'); + cy.contains('USB plugged-in rule'); + cy.contains('High'); cy.contains('Windows'); - cy.contains(indexName); ruleTags.forEach((tag) => { cy.contains(tag); @@ -147,7 +148,7 @@ describe('Findings', () => { it('opens rule details flyout when rule name inside accordion drop down is clicked', () => { // filter table to show only sample_detector findings - cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector'); + cy.get(`input[placeholder="Search findings"]`).ospSearch(indexName); // open Finding details flyout via finding id link. cy.wait essential, timeout insufficient. cy.getTableFirstRow('[data-test-subj="view-details-icon"]').then(($el) => { @@ -155,53 +156,13 @@ describe('Findings', () => { }); // Click rule link - cy.get(`[data-test-subj="finding-details-flyout-USB Device Plugged-details"]`).click({ + cy.get(`[data-test-subj="finding-details-flyout-${ruleName}-details"]`).click({ force: true, }); // Validate flyout appearance - cy.get('[data-test-subj="rule_flyout_USB Device Plugged"]').within(() => { - cy.get('[data-test-subj="rule_flyout_rule_name"]').contains('USB Device Plugged'); - }); - }); - - it('...can delete detector', () => { - // Visit Detectors page - cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); - cy.waitForPageLoad('detectors', { - contains: 'Threat detectors', - }); - - // filter table to show only sample_detector findings - cy.get(`input[placeholder="Search threat detectors"]`).ospSearch('sample_detector'); - - // intercept detectors and rules requests - cy.intercept('detectors/_search').as('getDetector'); - cy.intercept('rules/_search?prePackaged=true').as('getPrePackagedRules'); - cy.intercept('rules/_search?prePackaged=false').as('getRules'); - - // Click on detector to be removed - cy.contains('sample_detector').click({ force: true }); - cy.waitForPageLoad('detector-details', { - contains: sample_detector.name, - }); - - // wait for detector details to load before continuing - cy.wait(['@getDetector', '@getPrePackagedRules', '@getRules']).then(() => { - // Click "Actions" button, the click "Delete" - cy.get('button.euiButton') - .contains('Actions') - .click({ force: true }) - .then(() => { - // Confirm arrival at detectors page - cy.get('[data-test-subj="editButton"]').contains('Delete').click({ force: true }); - - // Search for sample_detector, presumably deleted - cy.get(`input[placeholder="Search threat detectors"]`).ospSearch('sample_detector'); - - // Confirm sample_detector no longer exists - cy.contains('There are no existing detectors.'); - }); + cy.get(`[data-test-subj="rule_flyout_${ruleName}"]`).within(() => { + cy.get('[data-test-subj="rule_flyout_rule_name"]').contains(ruleName); }); }); diff --git a/cypress/support/helpers.ts b/cypress/support/helpers.ts index 138d62f93..d7ca86ab2 100644 --- a/cypress/support/helpers.ts +++ b/cypress/support/helpers.ts @@ -1,3 +1,11 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import sample_detector from '../fixtures/integration_tests/detector/create_usb_detector_data.json'; +import { OPENSEARCH_DASHBOARDS_URL } from './constants'; + export const getElementByText = (locator: string, text: string) => locator ? cy.get(locator).filter(`:contains("${text}")`).should('be.visible') @@ -74,3 +82,75 @@ export const validateTable = ( } }); }; + +export const createDetector = ( + detectorName: string, + indexName: string, + indexSettings: any, + indexMappings: any, + ruleSettings: any, + indexDoc: any, + indexDocsCount: number = 1 +) => { + const detectorConfigAlertCondition = `${detectorName} alert condition`; + const detectorConfig = { + ...sample_detector, + name: detectorName, + inputs: [ + { + detector_input: { + ...sample_detector.inputs[0].detector_input, + description: `Description for ${detectorName}`, + indices: [indexName], + }, + }, + ], + triggers: [ + { + ...sample_detector.triggers[0], + name: detectorConfigAlertCondition, + }, + ], + }; + + const cySubject = cy + .cleanUpTests() + // Create test index + .then(() => cy.createIndex(indexName, indexSettings)) + + // Create field mappings + .then(() => + cy.createAliasMappings(indexName, detectorConfig.detector_type, indexMappings, true) + ) + + // Create test detector + .then(() => { + cy.createRule(ruleSettings) + .then((response) => { + detectorConfig.inputs[0].detector_input.custom_rules[0].id = response.body.response._id; + detectorConfig.triggers[0].ids.push(response.body.response._id); + }) + .then(() => cy.createDetector(detectorConfig)); + }) + + .then(() => { + // Go to the detectors table page + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); + + // Filter table to only show the test detector + cy.get(`input[type="search"]`).type(`${detectorConfig.name}{enter}`); + + // Confirm detector was created + cy.get('tbody > tr').should(($tr) => { + expect($tr, 'detector name').to.contain(detectorConfig.name); + }); + }); + + // Ingest documents to the test index + for (let i = 0; i < indexDocsCount; i++) { + cy.insertDocumentToIndex(indexName, '', indexDoc); + } + + cySubject.detector = detectorConfig; + return cySubject; +}; From c0056fec37f9170d4aff6fb2a1b93b95223b05d6 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 24 Apr 2023 18:50:21 +0200 Subject: [PATCH 50/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/support/helpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cypress/support/helpers.ts b/cypress/support/helpers.ts index d7ca86ab2..d4081afd1 100644 --- a/cypress/support/helpers.ts +++ b/cypress/support/helpers.ts @@ -44,8 +44,10 @@ export const selectComboboxItem = (combo: any, items: string | string[]) => { }); }; -export const clearCombobox = (combo: any) => +export const clearCombobox = (combo: any) => { combo.parentsUntil('.euiComboBox__inputWrap').siblings().find('button').eq(0).click(); + combo.type('{selectall}{backspace}{backspace}').clear(); +}; export const validateDetailsItem = (label: string, value: string) => { getElementByText('.euiFlexItem label', label).parent().siblings().contains(value); From 1c12d60694549b28f0980cce63a87ce6b4cdaa6d Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 24 Apr 2023 19:59:46 +0200 Subject: [PATCH 51/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/support/helpers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cypress/support/helpers.ts b/cypress/support/helpers.ts index d4081afd1..17d452223 100644 --- a/cypress/support/helpers.ts +++ b/cypress/support/helpers.ts @@ -45,8 +45,9 @@ export const selectComboboxItem = (combo: any, items: string | string[]) => { }; export const clearCombobox = (combo: any) => { - combo.parentsUntil('.euiComboBox__inputWrap').siblings().find('button').eq(0).click(); - combo.type('{selectall}{backspace}{backspace}').clear(); + combo.parents('.euiComboBox__inputWrap').within(() => { + cy.get('.euiBadge').find('.euiBadge__iconButton').click({ force: true, multiple: true }); + }); }; export const validateDetailsItem = (label: string, value: string) => { From 036d5dccf590e788d5903e41432287158a1b91e0 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 24 Apr 2023 20:08:04 +0200 Subject: [PATCH 52/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 322a19a41..1bec74b54 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -296,6 +296,7 @@ describe('Detectors', () => { }); it('...basic details can be edited', () => { + cy.intercept('GET', '/indices').as('getIndices'); openDetectorDetails(detectorName); editDetectorDetails(detectorName, 'Detector details'); @@ -304,6 +305,7 @@ describe('Detectors', () => { getElementByText('.euiTitle', 'Edit detector details'); }); + cy.wait('@getIndices'); getNameField().type('{selectall}{backspace}').type('test detector edited'); getTextareaByLabel('Description - optional').type('Edited description'); @@ -362,6 +364,7 @@ describe('Detectors', () => { }); it('...should update field mappings if data source is changed', () => { + cy.intercept('GET', '/indices').as('getIndices'); openDetectorDetails(detectorName); editDetectorDetails(detectorName, 'Detector details'); @@ -370,6 +373,7 @@ describe('Detectors', () => { getElementByText('.euiTitle', 'Edit detector details'); }); + cy.wait('@getIndices'); cy.get('.reviewFieldMappings').should('not.exist'); clearCombobox(getDataSourceField()); From e2172d4b648fe366ddf2f22b4c65787a2c9a926e Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 25 Apr 2023 08:24:36 +0200 Subject: [PATCH 53/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 1bec74b54..d725ee993 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -60,12 +60,11 @@ const openDetectorDetails = (detectorName) => { const editDetectorDetails = (detectorName, panelTitle) => { urlShouldContain('detector-details').then(() => { getElementByText('.euiTitle', detectorName); + getElementByText('.euiPanel .euiTitle', panelTitle); getElementByText('.euiPanel .euiTitle', panelTitle) .parent() .siblings() - .find('button') - .contains('Edit') - .click(); + .within(() => cy.get('button').contains('Edit').click()); }); }; From c63e602ef0f748590b923b733521fb97c8b6b30c Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 25 Apr 2023 08:37:51 +0200 Subject: [PATCH 54/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/support/helpers.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cypress/support/helpers.ts b/cypress/support/helpers.ts index 17d452223..e39b11e0a 100644 --- a/cypress/support/helpers.ts +++ b/cypress/support/helpers.ts @@ -39,7 +39,10 @@ export const selectComboboxItem = (combo: any, items: string | string[]) => { items = [items]; } items.map((item) => - cy.get('.euiComboBoxOptionsList__rowWrap').find('button').contains(item).click() + cy.get('.euiComboBoxOptionsList__rowWrap').within(() => { + cy.get('button').contains(item).should('be.visible'); + cy.get('button').contains(item).click(); + }) ); }); }; From 3c22dd872ae34cc2662912216560414cc8080b3a Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 25 Apr 2023 08:40:09 +0200 Subject: [PATCH 55/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/support/helpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cypress/support/helpers.ts b/cypress/support/helpers.ts index e39b11e0a..ea5b77936 100644 --- a/cypress/support/helpers.ts +++ b/cypress/support/helpers.ts @@ -49,7 +49,9 @@ export const selectComboboxItem = (combo: any, items: string | string[]) => { export const clearCombobox = (combo: any) => { combo.parents('.euiComboBox__inputWrap').within(() => { - cy.get('.euiBadge').find('.euiBadge__iconButton').click({ force: true, multiple: true }); + cy.get('.euiBadge').within(() => { + cy.get('.euiBadge__iconButton').click({ force: true, multiple: true }); + }); }); }; From 9b30a64e9a783085d58296ad262bf78d87b6de69 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 25 Apr 2023 09:46:52 +0200 Subject: [PATCH 56/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 29 +++++++------------------ 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index d725ee993..6b51fc4c4 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -118,8 +118,7 @@ const createDetector = (detectorName, dataSource, expectFailure) => { .find('input') .blur(); - getElementByTestSubject('security-levels-combo-box').click(); - getElementByText('.euiComboBoxOption__content', '1 (Highest)').click({ force: true }); + selectComboboxItem(getInputByLabel('Specify alert severity'), '1 (Highest)'); // go to review page getNextButton().click({ force: true }); @@ -284,16 +283,16 @@ describe('Detectors', () => { ); }); - it('...can be created', () => { - createDetector(detectorName, cypressIndexDns, false); - getElementByText('.euiCallOut', 'Detector created successfully'); - }); - it('...can fail creation', () => { createDetector(`${detectorName}_fail`, '.kibana_1', true); getElementByText('.euiCallOut', 'Create detector failed.'); }); + it('...can be created', () => { + createDetector(detectorName, cypressIndexDns, false); + getElementByText('.euiCallOut', 'Detector created successfully'); + }); + it('...basic details can be edited', () => { cy.intercept('GET', '/indices').as('getIndices'); openDetectorDetails(detectorName); @@ -321,20 +320,6 @@ describe('Detectors', () => { validateDetailsItem('Description', 'Edited description'); validateDetailsItem('Detector schedule', 'Every 10 hours'); validateDetailsItem('Data source', cypressIndexWindows); - - editDetectorDetails(detectorName, 'Detector details'); - - urlShouldContain('edit-detector-details').then(() => { - getElementByText('.euiTitle', 'Edit detector details'); - }); - - clearCombobox(getDataSourceField()); - selectComboboxItem(getDataSourceField(), cypressIndexDns); - - getElementByText('button', 'Save changes').click({ force: true }); - urlShouldContain('detector-details').then(() => { - validateDetailsItem('Data source', cypressIndexDns); - }); }); }); @@ -381,6 +366,8 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('be.visible'); validateEditFieldMappingsPanel(3, dns_mapping_fields); + + getElementByText('button', 'Save changes').click({ force: true }); }); it('...should show field mappings if rule selection is changed', () => { From 00ca405dc7069fbbf96bc95b8c8b796e58f44547 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 25 Apr 2023 10:39:32 +0200 Subject: [PATCH 57/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 40 +++++++++++++++++++++---- cypress/support/helpers.ts | 28 ++++++++++++----- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 6b51fc4c4..dd4cd2154 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -24,6 +24,7 @@ import { getButtonByText, getTextareaByLabel, } from '../support/helpers'; +import _ from 'lodash'; const cypressIndexDns = 'cypress-index-dns'; const cypressIndexWindows = 'cypress-index-windows'; @@ -68,7 +69,7 @@ const editDetectorDetails = (detectorName, panelTitle) => { }); }; -const validateEditFieldMappingsPanel = (length, mappings) => +const validateAutomaticFieldMappingsPanel = (length, mappings) => cy.get('.editFieldMappings').within(() => { cy.get('.euiAccordion__triggerWrapper button').then(($btn) => { cy.get($btn).contains(`Automatically mapped fields (${length})`); @@ -86,6 +87,19 @@ const validateEditFieldMappingsPanel = (length, mappings) => }); }); +const validatePendingFieldMappingsPanel = (mappings) => { + cy.get('.editFieldMappings').within(() => { + // Pending field mappings + getElementByText('.euiTitle', 'Pending field mappings') + .parents('.euiPanel') + .within(() => { + cy.get('.euiBasicTable').then(($table) => { + validateTable($table, null, mappings); + }); + }); + }); +}; + const createDetector = (detectorName, dataSource, expectFailure) => { getCreateDetectorButton().click({ force: true }); @@ -348,6 +362,7 @@ describe('Detectors', () => { }); it('...should update field mappings if data source is changed', () => { + cy.intercept('mappings/view').as('getMappingsView'); cy.intercept('GET', '/indices').as('getIndices'); openDetectorDetails(detectorName); @@ -364,8 +379,15 @@ describe('Detectors', () => { getDataSourceField().should('not.have.value'); getDataSourceField().type(`${cypressIndexDns}{enter}`); - cy.get('.reviewFieldMappings').should('be.visible'); - validateEditFieldMappingsPanel(3, dns_mapping_fields); + cy.wait('@getMappingsView').then((interception) => { + cy.get('.reviewFieldMappings').should('be.visible'); + const properties = interception.response.body.response.properties; + if (_.isEmpty(properties)) { + validatePendingFieldMappingsPanel(dns_mapping_fields); + } else { + validateAutomaticFieldMappingsPanel(3, dns_mapping_fields); + } + }); getElementByText('button', 'Save changes').click({ force: true }); }); @@ -390,9 +412,15 @@ describe('Detectors', () => { '[data-test-subj="edit-detector-rules-table"] table thead tr:first th:first button' ).click({ force: true }); - cy.wait('@getMappingsView'); - cy.get('.reviewFieldMappings').should('be.visible'); - validateEditFieldMappingsPanel(3, dns_mapping_fields); + cy.wait('@getMappingsView').then((interception) => { + cy.get('.reviewFieldMappings').should('be.visible'); + const properties = interception.response.body.response.properties; + if (_.isEmpty(properties)) { + validatePendingFieldMappingsPanel(dns_mapping_fields); + } else { + validateAutomaticFieldMappingsPanel(3, dns_mapping_fields); + } + }); }); it('...can be deleted', () => { diff --git a/cypress/support/helpers.ts b/cypress/support/helpers.ts index ea5b77936..2b277d6bf 100644 --- a/cypress/support/helpers.ts +++ b/cypress/support/helpers.ts @@ -50,7 +50,13 @@ export const selectComboboxItem = (combo: any, items: string | string[]) => { export const clearCombobox = (combo: any) => { combo.parents('.euiComboBox__inputWrap').within(() => { cy.get('.euiBadge').within(() => { - cy.get('.euiBadge__iconButton').click({ force: true, multiple: true }); + cy.get('.euiBadge__iconButton').then(($btn) => { + if ($btn.length && $btn.length > 1) { + cy.get($btn).click({ force: true, multiple: true }); + } else { + cy.get($btn).click({ force: true }); + } + }); }); }); }; @@ -80,14 +86,20 @@ export const validateTable = ( .should('be.visible') .find('table tbody') .find('tr') - .should('have.length', length) - .within(($tr: any) => { - if (dataMap) { - for (let logField in dataMap) { - cy.get($tr).find('td').contains(logField); - cy.get($tr).find('td').contains(dataMap[logField]); - } + .then(($tr) => { + const validateLength = length !== null; + if (validateLength) { + cy.get($tr).should('have.length', length); } + + cy.get($tr).within(($tr: any) => { + if (dataMap) { + for (let logField in dataMap) { + cy.get($tr).find('td').contains(logField); + validateLength && cy.get($tr).find('td').contains(dataMap[logField]); + } + } + }); }); }; From dbf3623292dde468da861176ae2565e04732db84 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 25 Apr 2023 11:59:21 +0200 Subject: [PATCH 58/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/support/helpers.ts | 75 +++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/cypress/support/helpers.ts b/cypress/support/helpers.ts index 2b277d6bf..3a4dd10ae 100644 --- a/cypress/support/helpers.ts +++ b/cypress/support/helpers.ts @@ -5,6 +5,7 @@ import sample_detector from '../fixtures/integration_tests/detector/create_usb_detector_data.json'; import { OPENSEARCH_DASHBOARDS_URL } from './constants'; +import _ from 'lodash'; export const getElementByText = (locator: string, text: string) => locator @@ -28,7 +29,12 @@ export const getTextareaByLabel = (label: string) => getInputByLabel(label, 'tex export const getElementByTestSubject = (subject: string) => cy.get(`[data-test-subj="${subject}"]`); -export const getRadioButtonById = (id: string) => cy.get(`input[id="${id}"]`); +export const getRadioButtonById = (id: string) => { + Cypress.log({ + message: `Find radio button by id: ${id}`, + }); + return cy.get(`input[id="${id}"]`); +}; export const selectComboboxItem = (combo: any, items: string | string[]) => { combo @@ -48,26 +54,32 @@ export const selectComboboxItem = (combo: any, items: string | string[]) => { }; export const clearCombobox = (combo: any) => { - combo.parents('.euiComboBox__inputWrap').within(() => { - cy.get('.euiBadge').within(() => { - cy.get('.euiBadge__iconButton').then(($btn) => { - if ($btn.length && $btn.length > 1) { - cy.get($btn).click({ force: true, multiple: true }); - } else { - cy.get($btn).click({ force: true }); - } + return combo + .parents('.euiComboBox__inputWrap') + .find('.euiBadge') + .then(($badge) => { + let numberOfBadges = $badge.length; + Cypress.log({ + message: `Number of combo badges to clear: ${numberOfBadges}`, }); + combo + .parents('.euiComboBox__inputWrap') + .find('input') + .focus() + .then(() => pressBackspaceKey(numberOfBadges)); }); - }); }; export const validateDetailsItem = (label: string, value: string) => { - getElementByText('.euiFlexItem label', label).parent().siblings().contains(value); + return getElementByText('.euiFlexItem label', label).parent().siblings().contains(value); }; export const urlShouldContain = (path: string) => cy.url().should('contain', `#/${path}`); -export const pressEnterKey = () => +export const pressEnterKey = () => { + Cypress.log({ + message: 'Enter key pressed', + }); Cypress.automation('remote:debugger:protocol', { command: 'Input.dispatchKeyEvent', params: { @@ -76,13 +88,47 @@ export const pressEnterKey = () => text: '\r', }, }); +}; + +export const pressBackspaceKey = (numberOfPresses: number = 1) => { + Cypress.log({ + message: 'Backspace key pressed', + }); + _.times(numberOfPresses, () => { + Cypress.automation('remote:debugger:protocol', { + command: 'Input.dispatchKeyEvent', + params: { + type: 'rawKeyDown', + keyCode: 8, + code: 'Backspace', + key: 'Backspace', + windowsVirtualKeyCode: 8, + }, + }); + cy.wait(10); + Cypress.automation('remote:debugger:protocol', { + command: 'Input.dispatchKeyEvent', + params: { + type: 'rawKeyUp', + keyCode: 8, + code: 'Backspace', + key: 'Backspace', + windowsVirtualKeyCode: 8, + }, + }); + }); +}; export const validateTable = ( container: any, // jqueryElement length: number, dataMap: { [key: string]: string } ) => { - cy.get(container) + Cypress.log({ + message: 'Validate table elements', + }); + return cy + .get(container) .should('be.visible') .find('table tbody') .find('tr') @@ -112,6 +158,9 @@ export const createDetector = ( indexDoc: any, indexDocsCount: number = 1 ) => { + Cypress.log({ + message: `Create new detector ${detectorName}`, + }); const detectorConfigAlertCondition = `${detectorName} alert condition`; const detectorConfig = { ...sample_detector, From 15fd5ceffeb37b48bcdc96fa5ddc80f7d77cca44 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 25 Apr 2023 13:21:04 +0200 Subject: [PATCH 59/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index dd4cd2154..3554fc50d 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -125,11 +125,12 @@ const createDetector = (detectorName, dataSource, expectFailure) => { getNextButton().click({ force: true }); // TEST ALERTS PAGE - getElementByText('.euiTitle', 'Set up alert triggers'); + getElementByText('.euiTitle.euiTitle--medium', 'Set up alert triggers'); getInputByPlaceholder('Enter a name to describe the alert condition').type('test_trigger'); getElementByTestSubject('alert-tags-combo-box') .type(`attack.defense_evasion{enter}`) .find('input') + .focus() .blur(); selectComboboxItem(getInputByLabel('Specify alert severity'), '1 (Highest)'); @@ -192,8 +193,11 @@ const createDetector = (detectorName, dataSource, expectFailure) => { getElementByText('button.euiTab', 'Alert triggers').should('be.visible').click(); validateAlertPanel('test_trigger'); + cy.intercept('GET', '/mappings?indexName').as('getMappingFields'); getElementByText('button.euiTab', 'Field mappings').should('be.visible').click(); if (!expectFailure) { + cy.wait('@getMappingFields'); + cy.wait(2000); getElementByText('.euiTitle', 'Field mapping') .parentsUntil('.euiPanel') .siblings() @@ -246,7 +250,7 @@ describe('Detectors', () => { getNextButton().should('be.disabled'); getNameField().should('be.empty'); - getNameField().type('text').blur(); + getNameField().type('text').focus().blur(); getNameField() .parentsUntil('.euiFormRow__fieldWrapper') @@ -257,6 +261,7 @@ describe('Detectors', () => { getNameField() .type(' and more text') + .focus() .blur() .parentsUntil('.euiFormRow__fieldWrapper') .siblings() @@ -273,6 +278,7 @@ describe('Detectors', () => { selectComboboxItem(getDataSourceField(), cypressIndexDns); getDataSourceField() + .focus() .blur() .parentsUntil('.euiFormRow__fieldWrapper') .find('.euiFormErrorText') @@ -288,7 +294,7 @@ describe('Detectors', () => { selectDnsLogType(); selectComboboxItem(getDataSourceField(), cypressIndexWindows); - getDataSourceField().blur(); + getDataSourceField().focus().blur(); cy.get('.euiCallOut') .should('be.visible') From a424bd6df8e2a5945e1c2ae2919615643ab22eb9 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 25 Apr 2023 18:13:44 +0200 Subject: [PATCH 60/64] updated create detectors cypress specs Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 3554fc50d..af2638108 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -420,6 +420,7 @@ describe('Detectors', () => { cy.wait('@getMappingsView').then((interception) => { cy.get('.reviewFieldMappings').should('be.visible'); + cy.wait(5000); const properties = interception.response.body.response.properties; if (_.isEmpty(properties)) { validatePendingFieldMappingsPanel(dns_mapping_fields); From c24eefc70c5df557d0d1f1b8ed71c4f494e26969 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Thu, 27 Apr 2023 13:33:32 +0200 Subject: [PATCH 61/64] refactored util methods into cypress commands Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 204 ++++++++---------- cypress/support/commands.js | 7 +- cypress/support/helpers.js | 275 ++++++++++++++++++++++++ cypress/support/helpers.ts | 225 ------------------- cypress/support/index.d.ts | 105 +++++++++ 5 files changed, 477 insertions(+), 339 deletions(-) create mode 100644 cypress/support/helpers.js delete mode 100644 cypress/support/helpers.ts diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index af2638108..255a76216 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -9,21 +9,6 @@ import sample_dns_index_settings from '../fixtures/sample_dns_index_settings.jso import dns_name_rule_data from '../fixtures/integration_tests/rule/create_dns_rule_with_name_selection.json'; import dns_type_rule_data from '../fixtures/integration_tests/rule/create_dns_rule_with_type_selection.json'; import dns_mapping_fields from '../fixtures/integration_tests/rule/sample_dns_field_mappings.json'; -import { - getInputByLabel, - getElementByTestSubject, - getElementByText, - getInputByPlaceholder, - getRadioButtonById, - selectComboboxItem, - validateDetailsItem, - urlShouldContain, - pressEnterKey, - validateTable, - clearCombobox, - getButtonByText, - getTextareaByLabel, -} from '../support/helpers'; import _ from 'lodash'; const cypressIndexDns = 'cypress-index-dns'; @@ -32,56 +17,53 @@ const detectorName = 'test detector'; const cypressDNSRule = dns_name_rule_data.title; -const getNameField = () => getInputByPlaceholder('Enter a name for the detector.'); +const getNameField = () => cy.getInputByPlaceholder('Enter a name for the detector.'); -const getNextButton = () => getButtonByText('Next'); +const getNextButton = () => cy.getButtonByText('Next'); -const getCreateDetectorButton = () => getButtonByText('Create detector'); +const getCreateDetectorButton = () => cy.getButtonByText('Create detector'); -const selectDnsLogType = () => getRadioButtonById('dns').click({ force: true }); +const selectDnsLogType = () => cy.getRadioButtonById('dns').click({ force: true }); const validateAlertPanel = (alertName) => - getElementByText('.euiTitle', 'Alert triggers') + cy + .getElementByText('.euiTitle', 'Alert triggers') .parentsUntil('.euiPanel') .siblings() .eq(2) - .within(() => getElementByText('button', alertName)); + .within(() => cy.getElementByText('button', alertName)); const dataSourceLabel = 'Select or input source indexes or index patterns'; -const getDataSourceField = () => getInputByLabel(dataSourceLabel); +const getDataSourceField = () => cy.getFieldByLabel(dataSourceLabel); const openDetectorDetails = (detectorName) => { - getInputByPlaceholder('Search threat detectors') - .type(`${detectorName}`) - .then(() => pressEnterKey()); - getElementByText('.euiTableCellContent button', detectorName).click(); + cy.getInputByPlaceholder('Search threat detectors').type(`${detectorName}`).pressEnterKey(); + cy.getElementByText('.euiTableCellContent button', detectorName).click(); }; const editDetectorDetails = (detectorName, panelTitle) => { - urlShouldContain('detector-details').then(() => { - getElementByText('.euiTitle', detectorName); - getElementByText('.euiPanel .euiTitle', panelTitle); - getElementByText('.euiPanel .euiTitle', panelTitle) + cy.urlShouldContain('detector-details').then(() => { + cy.getElementByText('.euiTitle', detectorName); + cy.getElementByText('.euiPanel .euiTitle', panelTitle); + cy.getElementByText('.euiPanel .euiTitle', panelTitle) .parent() .siblings() .within(() => cy.get('button').contains('Edit').click()); }); }; -const validateAutomaticFieldMappingsPanel = (length, mappings) => +const validateAutomaticFieldMappingsPanel = (mappings) => cy.get('.editFieldMappings').within(() => { cy.get('.euiAccordion__triggerWrapper button').then(($btn) => { - cy.get($btn).contains(`Automatically mapped fields (${length})`); + cy.get($btn).contains(`Automatically mapped fields (${mappings.length})`); // first check if the accordion is expanded, if not than expand the accordion if ($btn[0].getAttribute('aria-expanded') === 'false') { cy.get($btn[0]) .click() .then(() => { - cy.get('.euiAccordion__childWrapper .euiBasicTable').then(($table) => { - validateTable($table, length, mappings); - }); + cy.get('.euiAccordion__childWrapper .euiBasicTable').validateTable(mappings); }); } }); @@ -90,12 +72,10 @@ const validateAutomaticFieldMappingsPanel = (length, mappings) => const validatePendingFieldMappingsPanel = (mappings) => { cy.get('.editFieldMappings').within(() => { // Pending field mappings - getElementByText('.euiTitle', 'Pending field mappings') + cy.getElementByText('.euiTitle', 'Pending field mappings') .parents('.euiPanel') .within(() => { - cy.get('.euiBasicTable').then(($table) => { - validateTable($table, null, mappings); - }); + cy.get('.euiBasicTable').validateTable(mappings); }); }); }; @@ -105,15 +85,15 @@ const createDetector = (detectorName, dataSource, expectFailure) => { // TEST DETAILS PAGE getNameField().type(detectorName); - selectComboboxItem(getDataSourceField(), dataSource); + getDataSourceField().selectComboboxItem(dataSource); selectDnsLogType(); - getElementByText('.euiAccordion .euiTitle', 'Detection rules (14 selected)') + cy.getElementByText('.euiAccordion .euiTitle', 'Detection rules (14 selected)') .click({ force: true, timeout: 5000 }) .then(() => cy.contains('.euiTable .euiTableRow', 'Dns')); - getElementByText('.euiAccordion .euiTitle', 'Configure field mapping - optional'); + cy.getElementByText('.euiAccordion .euiTitle', 'Configure field mapping - optional'); cy.get('[aria-controls="mappedTitleFieldsAccordion"]').then(($btn) => { // first check if the accordion is expanded, if not than expand the accordion if ($btn && $btn[0] && $btn[0].getAttribute('aria-expanded') === 'false') { @@ -125,39 +105,39 @@ const createDetector = (detectorName, dataSource, expectFailure) => { getNextButton().click({ force: true }); // TEST ALERTS PAGE - getElementByText('.euiTitle.euiTitle--medium', 'Set up alert triggers'); - getInputByPlaceholder('Enter a name to describe the alert condition').type('test_trigger'); - getElementByTestSubject('alert-tags-combo-box') + cy.getElementByText('.euiTitle.euiTitle--medium', 'Set up alert triggers'); + cy.getInputByPlaceholder('Enter a name to describe the alert condition').type('test_trigger'); + cy.getElementByTestSubject('alert-tags-combo-box') .type(`attack.defense_evasion{enter}`) .find('input') .focus() .blur(); - selectComboboxItem(getInputByLabel('Specify alert severity'), '1 (Highest)'); + cy.getFieldByLabel('Specify alert severity').selectComboboxItem('1 (Highest)'); // go to review page getNextButton().click({ force: true }); // TEST REVIEW AND CREATE PAGE - getElementByText('.euiTitle', 'Review and create'); - getElementByText('.euiTitle', 'Detector details'); - getElementByText('.euiTitle', 'Field mapping'); - getElementByText('.euiTitle', 'Alert triggers'); - - validateDetailsItem('Detector name', detectorName); - validateDetailsItem('Description', '-'); - validateDetailsItem('Detector schedule', 'Every 1 minute'); - validateDetailsItem('Detection rules', '14'); - validateDetailsItem('Created at', '-'); - validateDetailsItem('Last updated time', '-'); - validateDetailsItem('Detector dashboard', 'Not available for this log type'); + cy.getElementByText('.euiTitle', 'Review and create'); + cy.getElementByText('.euiTitle', 'Detector details'); + cy.getElementByText('.euiTitle', 'Field mapping'); + cy.getElementByText('.euiTitle', 'Alert triggers'); + + cy.validateDetailsItem('Detector name', detectorName); + cy.validateDetailsItem('Description', '-'); + cy.validateDetailsItem('Detector schedule', 'Every 1 minute'); + cy.validateDetailsItem('Detection rules', '14'); + cy.validateDetailsItem('Created at', '-'); + cy.validateDetailsItem('Last updated time', '-'); + cy.validateDetailsItem('Detector dashboard', 'Not available for this log type'); if (!expectFailure) { - getElementByText('.euiTitle', 'Field mapping') + cy.getElementByText('.euiTitle', 'Field mapping') .parentsUntil('.euiPanel') .siblings() .eq(2) - .then(($el) => validateTable($el, 3, dns_mapping_fields)); + .validateTable(Object.entries(dns_mapping_fields)); } validateAlertPanel('test_trigger'); @@ -166,7 +146,7 @@ const createDetector = (detectorName, dataSource, expectFailure) => { cy.intercept('POST', '/detectors').as('createDetectorRequest'); // create the detector - getElementByText('button', 'Create').click({ force: true }); + cy.getElementByText('button', 'Create').click({ force: true }); // TEST DETECTOR DETAILS PAGE cy.wait('@createMappingsRequest'); @@ -178,31 +158,31 @@ const createDetector = (detectorName, dataSource, expectFailure) => { cy.url() .should('contain', detectorId) .then(() => { - getElementByText('.euiCallOut', `Detector created successfully: ${detectorName}`); + cy.getElementByText('.euiCallOut', `Detector created successfully: ${detectorName}`); // Confirm detector state - getElementByText('.euiTitle', detectorName); - getElementByText('.euiHealth', 'Active').then(() => { - validateDetailsItem('Detector name', detectorName); - validateDetailsItem('Description', '-'); - validateDetailsItem('Detector schedule', 'Every 1 minute'); - validateDetailsItem('Detection rules', '14'); - validateDetailsItem('Detector dashboard', 'Not available for this log type'); + cy.getElementByText('.euiTitle', detectorName); + cy.getElementByText('.euiHealth', 'Active').then(() => { + cy.validateDetailsItem('Detector name', detectorName); + cy.validateDetailsItem('Description', '-'); + cy.validateDetailsItem('Detector schedule', 'Every 1 minute'); + cy.validateDetailsItem('Detection rules', '14'); + cy.validateDetailsItem('Detector dashboard', 'Not available for this log type'); cy.wait(5000); // waiting for the page to be reloaded after pushing detector id into route - getElementByText('button.euiTab', 'Alert triggers').should('be.visible').click(); + cy.getElementByText('button.euiTab', 'Alert triggers').should('be.visible').click(); validateAlertPanel('test_trigger'); cy.intercept('GET', '/mappings?indexName').as('getMappingFields'); - getElementByText('button.euiTab', 'Field mappings').should('be.visible').click(); + cy.getElementByText('button.euiTab', 'Field mappings').should('be.visible').click(); if (!expectFailure) { cy.wait('@getMappingFields'); cy.wait(2000); - getElementByText('.euiTitle', 'Field mapping') + cy.getElementByText('.euiTitle', 'Field mapping') .parentsUntil('.euiPanel') .siblings() .eq(2) - .then(($el) => validateTable($el, 3, dns_mapping_fields)); + .validateTable(Object.entries(dns_mapping_fields)); } }); }); @@ -276,7 +256,7 @@ describe('Detectors', () => { .contains('Select an input source'); getNextButton().should('be.disabled'); - selectComboboxItem(getDataSourceField(), cypressIndexDns); + getDataSourceField().selectComboboxItem(cypressIndexDns); getDataSourceField() .focus() .blur() @@ -289,11 +269,11 @@ describe('Detectors', () => { it('...should show mappings warning', () => { getCreateDetectorButton().click({ force: true }); - selectComboboxItem(getDataSourceField(), cypressIndexDns); + getDataSourceField().selectComboboxItem(cypressIndexDns); selectDnsLogType(); - selectComboboxItem(getDataSourceField(), cypressIndexWindows); + getDataSourceField().selectComboboxItem(cypressIndexWindows); getDataSourceField().focus().blur(); cy.get('.euiCallOut') @@ -305,12 +285,12 @@ describe('Detectors', () => { it('...can fail creation', () => { createDetector(`${detectorName}_fail`, '.kibana_1', true); - getElementByText('.euiCallOut', 'Create detector failed.'); + cy.getElementByText('.euiCallOut', 'Create detector failed.'); }); it('...can be created', () => { createDetector(detectorName, cypressIndexDns, false); - getElementByText('.euiCallOut', 'Detector created successfully'); + cy.getElementByText('.euiCallOut', 'Detector created successfully'); }); it('...basic details can be edited', () => { @@ -319,27 +299,27 @@ describe('Detectors', () => { editDetectorDetails(detectorName, 'Detector details'); - urlShouldContain('edit-detector-details').then(() => { - getElementByText('.euiTitle', 'Edit detector details'); + cy.urlShouldContain('edit-detector-details').then(() => { + cy.getElementByText('.euiTitle', 'Edit detector details'); }); cy.wait('@getIndices'); getNameField().type('{selectall}{backspace}').type('test detector edited'); - getTextareaByLabel('Description - optional').type('Edited description'); + cy.getTextareaByLabel('Description - optional').type('Edited description'); - clearCombobox(getDataSourceField()); - selectComboboxItem(getDataSourceField(), cypressIndexWindows); + getDataSourceField().clearCombobox(); + getDataSourceField().selectComboboxItem(cypressIndexWindows); - getInputByLabel('Run every').type('{selectall}{backspace}').type('10'); - getInputByLabel('Run every', 'select').select('Hours'); + cy.getFieldByLabel('Run every').type('{selectall}{backspace}').type('10'); + cy.getFieldByLabel('Run every', 'select').select('Hours'); - getElementByText('button', 'Save changes').click({ force: true }); + cy.getElementByText('button', 'Save changes').click({ force: true }); - urlShouldContain('detector-details').then(() => { - validateDetailsItem('Detector name', 'test detector edited'); - validateDetailsItem('Description', 'Edited description'); - validateDetailsItem('Detector schedule', 'Every 10 hours'); - validateDetailsItem('Data source', cypressIndexWindows); + cy.urlShouldContain('detector-details').then(() => { + cy.validateDetailsItem('Detector name', 'test detector edited'); + cy.validateDetailsItem('Description', 'Edited description'); + cy.validateDetailsItem('Detector schedule', 'Every 10 hours'); + cy.validateDetailsItem('Data source', cypressIndexWindows); }); }); @@ -347,23 +327,21 @@ describe('Detectors', () => { openDetectorDetails(detectorName); editDetectorDetails(detectorName, 'Active rules'); - getElementByText('.euiTitle', 'Detection rules (14)'); + cy.getElementByText('.euiTitle', 'Detection rules (14)'); - getInputByPlaceholder('Search...') - .type(`${cypressDNSRule}`) - .then(() => pressEnterKey()); + cy.getInputByPlaceholder('Search...').type(`${cypressDNSRule}`).pressEnterKey(); - getElementByText('.euiTableCellContent button', cypressDNSRule) + cy.getElementByText('.euiTableCellContent button', cypressDNSRule) .parents('td') .prev() .find('.euiTableCellContent button') .click(); - getElementByText('.euiTitle', 'Detection rules (13)'); - getElementByText('button', 'Save changes').click({ force: true }); - urlShouldContain('detector-details').then(() => { - getElementByText('.euiTitle', detectorName); - getElementByText('.euiPanel .euiTitle', 'Active rules (13)'); + cy.getElementByText('.euiTitle', 'Detection rules (13)'); + cy.getElementByText('button', 'Save changes').click({ force: true }); + cy.urlShouldContain('detector-details').then(() => { + cy.getElementByText('.euiTitle', detectorName); + cy.getElementByText('.euiPanel .euiTitle', 'Active rules (13)'); }); }); @@ -374,14 +352,14 @@ describe('Detectors', () => { editDetectorDetails(detectorName, 'Detector details'); - urlShouldContain('edit-detector-details').then(() => { - getElementByText('.euiTitle', 'Edit detector details'); + cy.urlShouldContain('edit-detector-details').then(() => { + cy.getElementByText('.euiTitle', 'Edit detector details'); }); cy.wait('@getIndices'); cy.get('.reviewFieldMappings').should('not.exist'); - clearCombobox(getDataSourceField()); + getDataSourceField().clearCombobox(); getDataSourceField().should('not.have.value'); getDataSourceField().type(`${cypressIndexDns}{enter}`); @@ -389,13 +367,13 @@ describe('Detectors', () => { cy.get('.reviewFieldMappings').should('be.visible'); const properties = interception.response.body.response.properties; if (_.isEmpty(properties)) { - validatePendingFieldMappingsPanel(dns_mapping_fields); + validatePendingFieldMappingsPanel(Object.entries(dns_mapping_fields)); } else { - validateAutomaticFieldMappingsPanel(3, dns_mapping_fields); + validateAutomaticFieldMappingsPanel(Object.entries(dns_mapping_fields)); } }); - getElementByText('button', 'Save changes').click({ force: true }); + cy.getElementByText('button', 'Save changes').click({ force: true }); }); it('...should show field mappings if rule selection is changed', () => { @@ -405,8 +383,8 @@ describe('Detectors', () => { editDetectorDetails(detectorName, 'Active rules'); - urlShouldContain('edit-detector-rules').then(() => { - getElementByText('.euiTitle', 'Edit detector rules'); + cy.urlShouldContain('edit-detector-rules').then(() => { + cy.getElementByText('.euiTitle', 'Edit detector rules'); }); cy.get('.reviewFieldMappings').should('not.exist'); @@ -423,9 +401,9 @@ describe('Detectors', () => { cy.wait(5000); const properties = interception.response.body.response.properties; if (_.isEmpty(properties)) { - validatePendingFieldMappingsPanel(dns_mapping_fields); + validatePendingFieldMappingsPanel(Object.entries(dns_mapping_fields)); } else { - validateAutomaticFieldMappingsPanel(3, dns_mapping_fields); + validateAutomaticFieldMappingsPanel(Object.entries(dns_mapping_fields)); } }); }); @@ -443,11 +421,11 @@ describe('Detectors', () => { cy.wait('@getCustomRules'); cy.wait('@getSigmaRules'); - getButtonByText('Actions') + cy.getButtonByText('Actions') .click({ force: true }) .then(() => { cy.intercept('/detectors').as('detectors'); - getElementByText('.euiContextMenuItem', 'Delete').click({ force: true }); + cy.getElementByText('.euiContextMenuItem', 'Delete').click({ force: true }); cy.wait('@detectors').then(() => { cy.contains('There are no existing detectors'); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index a318227df..e81b9193e 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -8,6 +8,7 @@ require('./detectors'); require('./rules'); require('./indexes'); require('./typings'); +require('./helpers'); // *********************************************** // This example commands.js shows you how to @@ -43,10 +44,14 @@ Cypress.Commands.overwrite('visit', (originalFn, url, options) => { username: Cypress.env('username'), password: Cypress.env('password'), }; + if (adsd) { + asd = new oasd(); + asd = asd.gte(); + } if (options) { options.auth = ADMIN_AUTH; } else { - options = { auth: ADMIN_AUTH }; + options = { auth: asd }; } // Add query parameters - select the default OSD tenant options.qs = { security_tenant: 'private' }; diff --git a/cypress/support/helpers.js b/cypress/support/helpers.js new file mode 100644 index 000000000..7c212a5bb --- /dev/null +++ b/cypress/support/helpers.js @@ -0,0 +1,275 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import sample_detector from '../fixtures/integration_tests/detector/create_usb_detector_data.json'; +import { NODE_API, OPENSEARCH_DASHBOARDS_URL } from './constants'; +import _ from 'lodash'; + +Cypress.Commands.add('getElementByText', (locator, text) => { + Cypress.log({ message: `Get element by text: ${text}` }); + return locator + ? cy.get(locator).filter(`:contains("${text}")`).should('be.visible') + : cy.contains(text).should('be.visible'); +}); + +Cypress.Commands.add('getButtonByText', (text) => { + Cypress.log({ message: `Get button by text: ${text}` }); + return cy.getElementByText('.euiButton', text); +}); + +Cypress.Commands.add('getInputByPlaceholder', (placeholder) => { + Cypress.log({ message: `Get input element by placeholder: ${placeholder}` }); + return cy.get(`input[placeholder="${placeholder}"]`); +}); + +Cypress.Commands.add('getComboboxByPlaceholder', (placeholder) => { + Cypress.log({ message: `Get combobox element by placeholder: ${placeholder}` }); + return cy + .getElementByText('.euiComboBoxPlaceholder', placeholder) + .siblings('.euiComboBox__input') + .find('input'); +}); + +Cypress.Commands.add('getFieldByLabel', (label, type = 'input') => { + Cypress.log({ message: `Get field by label: ${label}` }); + return cy.getElementByText('.euiFormRow__labelWrapper', label).siblings().find(type); +}); + +Cypress.Commands.add('getTextareaByLabel', (label) => { + Cypress.log({ message: `Get textarea by label: ${label}` }); + return cy.getFieldByLabel(label, 'textarea'); +}); + +Cypress.Commands.add('getElementByTestSubject', (subject) => { + Cypress.log({ message: `Get element by test subject: ${subject}` }); + return cy.get(`[data-test-subj="${subject}"]`); +}); + +Cypress.Commands.add('getRadioButtonById', (id) => { + Cypress.log({ message: `Get radio button by id: ${id}` }); + return cy.get(`input[id="${id}"]`); +}); + +Cypress.Commands.add( + 'selectComboboxItem', + { + prevSubject: true, + }, + (subject, items) => { + if (typeof items === 'string') { + items = [items]; + } + Cypress.log({ message: `Select combobox items: ${items.join(' | ')}` }); + cy.wrap(subject) + .focus() + .click({ force: true }) + .then(() => { + items.map((item) => + cy.get('.euiComboBoxOptionsList__rowWrap').within(() => { + cy.get('button').contains(item).should('be.visible'); + cy.get('button').contains(item).click(); + }) + ); + }); + } +); + +Cypress.Commands.add( + 'clearCombobox', + { + prevSubject: true, + }, + (subject) => { + Cypress.log({ message: `Clear combobox` }); + return cy + .wrap(subject) + .parents('.euiComboBox__inputWrap') + .find('.euiBadge') + .then(($badge) => { + let numberOfBadges = $badge.length; + Cypress.log({ + message: `Number of combo badges to clear: ${numberOfBadges}`, + }); + + cy.wrap(subject) + .parents('.euiComboBox__inputWrap') + .find('input') + .focus() + .pressBackspaceKey(numberOfBadges); + }); + } +); + +Cypress.Commands.add('validateDetailsItem', (label, value) => { + Cypress.log({ message: `Validate details item by label: ${label} and value: ${value}` }); + return cy.getElementByText('.euiFlexItem label', label).parent().siblings().contains(value); +}); + +Cypress.Commands.add('urlShouldContain', (path) => { + Cypress.log({ message: `Url should contain path: ${path}` }); + return cy.url().should('contain', `#/${path}`); +}); + +Cypress.Commands.add( + 'pressEnterKey', + { + prevSubject: true, + }, + (subject) => { + Cypress.log({ + message: 'Enter key pressed', + }); + Cypress.automation('remote:debugger:protocol', { + command: 'Input.dispatchKeyEvent', + params: { + type: 'char', + unmodifiedText: '\r', + text: '\r', + }, + }); + + return subject; + } +); + +Cypress.Commands.add( + 'pressBackspaceKey', + { + prevSubject: true, + }, + (subject, numberOfPresses = 1) => { + Cypress.log({ + message: 'Backspace key pressed', + }); + _.times(numberOfPresses, () => { + Cypress.automation('remote:debugger:protocol', { + command: 'Input.dispatchKeyEvent', + params: { + type: 'rawKeyDown', + keyCode: 8, + code: 'Backspace', + key: 'Backspace', + windowsVirtualKeyCode: 8, + }, + }); + cy.wait(10); + Cypress.automation('remote:debugger:protocol', { + command: 'Input.dispatchKeyEvent', + params: { + type: 'rawKeyUp', + keyCode: 8, + code: 'Backspace', + key: 'Backspace', + windowsVirtualKeyCode: 8, + }, + }); + }); + } +); + +Cypress.Commands.add( + 'validateTable', + { + prevSubject: true, + }, + (subject, data) => { + Cypress.log({ + message: 'Validate table elements', + }); + return cy + .wrap(subject) + .should('be.visible') + .find('tbody') + .find('tr') + .then(($tr) => { + const length = Object.keys(data).length; + length && cy.get($tr).should('have.length', length); + + cy.get($tr).within(($tr) => { + data.map((rowData) => { + rowData.map((tdData) => { + cy.get($tr).find('td').contains(tdData); + }); + }); + }); + }); + } +); + +export const createDetector = ( + detectorName, + indexName, + indexSettings, + indexMappings, + ruleSettings, + indexDoc, + indexDocsCount = 1 +) => { + Cypress.log({ + message: `Create new detector ${detectorName}`, + }); + const detectorConfigAlertCondition = `${detectorName} alert condition`; + const detectorConfig = { + ...sample_detector, + name: detectorName, + inputs: [ + { + detector_input: { + ...sample_detector.inputs[0].detector_input, + description: `Description for ${detectorName}`, + indices: [indexName], + }, + }, + ], + triggers: [ + { + ...sample_detector.triggers[0], + name: detectorConfigAlertCondition, + }, + ], + }; + + const cySubject = cy + .cleanUpTests() + // Create test index + .then(() => cy.createIndex(indexName, indexSettings)) + + // Create field mappings + .then(() => + cy.createAliasMappings(indexName, detectorConfig.detector_type, indexMappings, true) + ) + + // Create rule + .then(() => { + cy.createRule(ruleSettings) + .then((response) => { + detectorConfig.inputs[0].detector_input.custom_rules[0].id = response.body.response._id; + detectorConfig.triggers[0].ids.push(response.body.response._id); + }) + // create the detector + .then(() => cy.createDetector(detectorConfig)); + }) + + .then(() => { + // Go to the detectors table page + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); + + // Filter table to only show the test detector + cy.get(`input[type="search"]`).type(`${detectorConfig.name}{enter}`); + + // Confirm detector was created + cy.get('tbody > tr').should(($tr) => { + expect($tr, 'detector name').to.contain(detectorConfig.name); + }); + }); + + // Ingest documents to the test index + for (let i = 0; i < indexDocsCount; i++) { + cy.insertDocumentToIndex(indexName, '', indexDoc); + } + + cySubject.detector = detectorConfig; + return cySubject; +}; diff --git a/cypress/support/helpers.ts b/cypress/support/helpers.ts deleted file mode 100644 index 3a4dd10ae..000000000 --- a/cypress/support/helpers.ts +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import sample_detector from '../fixtures/integration_tests/detector/create_usb_detector_data.json'; -import { OPENSEARCH_DASHBOARDS_URL } from './constants'; -import _ from 'lodash'; - -export const getElementByText = (locator: string, text: string) => - locator - ? cy.get(locator).filter(`:contains("${text}")`).should('be.visible') - : cy.contains(text).should('be.visible'); - -export const getButtonByText = (text: string) => getElementByText('.euiButton', text); - -export const getInputByPlaceholder = (placeholder: string) => - cy.get(`input[placeholder="${placeholder}"]`); - -export const getComboboxByPlaceholder = (placeholder: string) => - getElementByText('.euiComboBoxPlaceholder', placeholder) - .siblings('.euiComboBox__input') - .find('input'); - -export const getInputByLabel = (label: string, type = 'input') => - getElementByText('.euiFormRow__labelWrapper', label).siblings().find(type); - -export const getTextareaByLabel = (label: string) => getInputByLabel(label, 'textarea'); - -export const getElementByTestSubject = (subject: string) => cy.get(`[data-test-subj="${subject}"]`); - -export const getRadioButtonById = (id: string) => { - Cypress.log({ - message: `Find radio button by id: ${id}`, - }); - return cy.get(`input[id="${id}"]`); -}; - -export const selectComboboxItem = (combo: any, items: string | string[]) => { - combo - .focus() - .click({ force: true }) - .then(() => { - if (typeof items === 'string') { - items = [items]; - } - items.map((item) => - cy.get('.euiComboBoxOptionsList__rowWrap').within(() => { - cy.get('button').contains(item).should('be.visible'); - cy.get('button').contains(item).click(); - }) - ); - }); -}; - -export const clearCombobox = (combo: any) => { - return combo - .parents('.euiComboBox__inputWrap') - .find('.euiBadge') - .then(($badge) => { - let numberOfBadges = $badge.length; - Cypress.log({ - message: `Number of combo badges to clear: ${numberOfBadges}`, - }); - combo - .parents('.euiComboBox__inputWrap') - .find('input') - .focus() - .then(() => pressBackspaceKey(numberOfBadges)); - }); -}; - -export const validateDetailsItem = (label: string, value: string) => { - return getElementByText('.euiFlexItem label', label).parent().siblings().contains(value); -}; - -export const urlShouldContain = (path: string) => cy.url().should('contain', `#/${path}`); - -export const pressEnterKey = () => { - Cypress.log({ - message: 'Enter key pressed', - }); - Cypress.automation('remote:debugger:protocol', { - command: 'Input.dispatchKeyEvent', - params: { - type: 'char', - unmodifiedText: '\r', - text: '\r', - }, - }); -}; - -export const pressBackspaceKey = (numberOfPresses: number = 1) => { - Cypress.log({ - message: 'Backspace key pressed', - }); - _.times(numberOfPresses, () => { - Cypress.automation('remote:debugger:protocol', { - command: 'Input.dispatchKeyEvent', - params: { - type: 'rawKeyDown', - keyCode: 8, - code: 'Backspace', - key: 'Backspace', - windowsVirtualKeyCode: 8, - }, - }); - cy.wait(10); - Cypress.automation('remote:debugger:protocol', { - command: 'Input.dispatchKeyEvent', - params: { - type: 'rawKeyUp', - keyCode: 8, - code: 'Backspace', - key: 'Backspace', - windowsVirtualKeyCode: 8, - }, - }); - }); -}; - -export const validateTable = ( - container: any, // jqueryElement - length: number, - dataMap: { [key: string]: string } -) => { - Cypress.log({ - message: 'Validate table elements', - }); - return cy - .get(container) - .should('be.visible') - .find('table tbody') - .find('tr') - .then(($tr) => { - const validateLength = length !== null; - if (validateLength) { - cy.get($tr).should('have.length', length); - } - - cy.get($tr).within(($tr: any) => { - if (dataMap) { - for (let logField in dataMap) { - cy.get($tr).find('td').contains(logField); - validateLength && cy.get($tr).find('td').contains(dataMap[logField]); - } - } - }); - }); -}; - -export const createDetector = ( - detectorName: string, - indexName: string, - indexSettings: any, - indexMappings: any, - ruleSettings: any, - indexDoc: any, - indexDocsCount: number = 1 -) => { - Cypress.log({ - message: `Create new detector ${detectorName}`, - }); - const detectorConfigAlertCondition = `${detectorName} alert condition`; - const detectorConfig = { - ...sample_detector, - name: detectorName, - inputs: [ - { - detector_input: { - ...sample_detector.inputs[0].detector_input, - description: `Description for ${detectorName}`, - indices: [indexName], - }, - }, - ], - triggers: [ - { - ...sample_detector.triggers[0], - name: detectorConfigAlertCondition, - }, - ], - }; - - const cySubject = cy - .cleanUpTests() - // Create test index - .then(() => cy.createIndex(indexName, indexSettings)) - - // Create field mappings - .then(() => - cy.createAliasMappings(indexName, detectorConfig.detector_type, indexMappings, true) - ) - - // Create test detector - .then(() => { - cy.createRule(ruleSettings) - .then((response) => { - detectorConfig.inputs[0].detector_input.custom_rules[0].id = response.body.response._id; - detectorConfig.triggers[0].ids.push(response.body.response._id); - }) - .then(() => cy.createDetector(detectorConfig)); - }) - - .then(() => { - // Go to the detectors table page - cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); - - // Filter table to only show the test detector - cy.get(`input[type="search"]`).type(`${detectorConfig.name}{enter}`); - - // Confirm detector was created - cy.get('tbody > tr').should(($tr) => { - expect($tr, 'detector name').to.contain(detectorConfig.name); - }); - }); - - // Ingest documents to the test index - for (let i = 0; i < indexDocsCount; i++) { - cy.insertDocumentToIndex(indexName, '', indexDoc); - } - - cySubject.detector = detectorConfig; - return cySubject; -}; diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index 104581c1e..2ffaed7e5 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -8,6 +8,111 @@ declare namespace Cypress { interface Chainable { + /** + * Returns element by its text + * @example + * cy.getElementByText('.euiTitle', 'Some title') + */ + getElementByText(locator: string, text: string): Chainable; + + /** + * Returns button by its text + * @example + * cy.getButtonByText('Button text') + */ + getButtonByText(text: string): Chainable; + + /** + * Returns input by its placeholder + * @example + * cy.getInputByPlaceholder('Search rules...') + */ + getInputByPlaceholder(placeholder: string): Chainable; + + /** + * Returns combobox input by its placeholder + * @example + * cy.getComboboxByPlaceholder('Select data input...') + */ + getComboboxByPlaceholder(placeholder: string): Chainable; + + /** + * Returns field input by label + * @example + * cy.getFieldByLabel('Detector name') + */ + getFieldByLabel(label: string, type?: string): Chainable; + + /** + * Returns textarea by label + * @example + * cy.getTextareaByLabel('Detector description') + */ + getTextareaByLabel(label: string): Chainable; + + /** + * Returns element by data-test-subj attribute value + * @example + * cy.getElementByTestSubject('alerts-input-element') + */ + getElementByTestSubject(subject: string): Chainable; + + /** + * Returns radio by id + * @example + * cy.getRadioButtonById('radioId') + */ + getRadioButtonById(id: string): Chainable; + + /** + * Selects combobox item(s) + * @example + * cy.get('combo).selectComboboxItem('some item value') + */ + selectComboboxItem(items: string | string[]): Chainable; + + /** + * Clears combobox value(s) + * @example + * cy.get('combo).clearCombobox() + */ + clearCombobox(): Chainable; + + /** + * Triggers enter key event on the focused element + * @example + * cy.pressEnterKey() + */ + pressEnterKey(): Chainable; + + /** + * Triggers backspace key event on the focused element + * @example + * cy.pressBackspaceKey() + */ + pressBackspaceKey(numberOfPresses?: number): Chainable; + + /** + * Validates details panel item + * @example + * cy.validateDetailsItem('Data source', '.index-name') + */ + validateDetailsItem(label: string, value: string): Chainable; + + /** + * Validates url path + * @example + * cy.urlShouldContain('/detector-details') + */ + urlShouldContain(path: string): Chainable; + + /** + * Validates table items + * @example + * cy.validateTable('/detector-details') + */ + validateTable(data: { [key: string]: string }[]): Chainable; + /** * Removes custom indices, detectors and rules * @example From c3decdd95a436b723d69ceb633accc8b65745008 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Thu, 27 Apr 2023 13:50:51 +0200 Subject: [PATCH 62/64] refactored util methods into cypress commands Signed-off-by: Jovan Cvetkovic --- test/jest.config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/jest.config.js b/test/jest.config.js index 62e48b983..5928281b4 100644 --- a/test/jest.config.js +++ b/test/jest.config.js @@ -37,6 +37,12 @@ module.exports = { ], clearMocks: true, testPathIgnorePatterns: ['/build/', '/node_modules/'], + transformIgnorePatterns: [ + // ignore all node_modules except those which require babel transforms to + // handle newer syntax like `??=` which is not already transformed by the + // package and not yet supported in the node version we use. + '[/\\\\]node_modules(?![\\/\\\\](vega-lite))[/\\\\].+\\.js$', + ], modulePathIgnorePatterns: ['securityAnalyticsDashboards'], testEnvironment: 'jsdom', snapshotSerializers: ['enzyme-to-json/serializer'], From 70a7b2c5619bdbd6ffe4f65437839bf9e332a072 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Wed, 3 May 2023 21:28:29 +0200 Subject: [PATCH 63/64] cypress tests Signed-off-by: Jovan Cvetkovic --- cypress/integration/1_detectors.spec.js | 49 +++++++++-------- cypress/support/helpers.js | 6 +-- .../containers/ConfigureFieldMapping.tsx | 52 ++++++++++--------- .../FieldMappings/EditFieldMapping.tsx | 52 ++++++++++--------- 4 files changed, 87 insertions(+), 72 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 255a76216..0ad469c2d 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -42,6 +42,26 @@ const openDetectorDetails = (detectorName) => { cy.getElementByText('.euiTableCellContent button', detectorName).click(); }; +const validateFieldMappingsTable = () => { + cy.wait('@getMappingsView').then((interception) => { + cy.wait(10000).then(() => { + cy.get('.reviewFieldMappings').should('be.visible'); + const properties = interception.response.body.response.properties; + const unmapped_field_aliases = interception.response.body.response.unmapped_field_aliases; + let mappingFields = {}; + unmapped_field_aliases.map((field) => { + mappingFields[field] = undefined; + }); + + if (_.isEmpty(properties)) { + validatePendingFieldMappingsPanel(Object.entries(mappingFields)); + } else { + validateAutomaticFieldMappingsPanel(Object.entries(properties)); + } + }); + }); +}; + const editDetectorDetails = (detectorName, panelTitle) => { cy.urlShouldContain('detector-details').then(() => { cy.getElementByText('.euiTitle', detectorName); @@ -63,7 +83,9 @@ const validateAutomaticFieldMappingsPanel = (mappings) => cy.get($btn[0]) .click() .then(() => { - cy.get('.euiAccordion__childWrapper .euiBasicTable').validateTable(mappings); + cy.getElementByTestSubject('auto-mapped-fields-table') + .find('.euiBasicTable') + .validateTable(mappings); }); } }); @@ -75,7 +97,9 @@ const validatePendingFieldMappingsPanel = (mappings) => { cy.getElementByText('.euiTitle', 'Pending field mappings') .parents('.euiPanel') .within(() => { - cy.get('.euiBasicTable').validateTable(mappings); + cy.getElementByTestSubject('pending-mapped-fields-table') + .find('.euiBasicTable') + .validateTable(mappings); }); }); }; @@ -363,15 +387,7 @@ describe('Detectors', () => { getDataSourceField().should('not.have.value'); getDataSourceField().type(`${cypressIndexDns}{enter}`); - cy.wait('@getMappingsView').then((interception) => { - cy.get('.reviewFieldMappings').should('be.visible'); - const properties = interception.response.body.response.properties; - if (_.isEmpty(properties)) { - validatePendingFieldMappingsPanel(Object.entries(dns_mapping_fields)); - } else { - validateAutomaticFieldMappingsPanel(Object.entries(dns_mapping_fields)); - } - }); + validateFieldMappingsTable(); cy.getElementByText('button', 'Save changes').click({ force: true }); }); @@ -396,16 +412,7 @@ describe('Detectors', () => { '[data-test-subj="edit-detector-rules-table"] table thead tr:first th:first button' ).click({ force: true }); - cy.wait('@getMappingsView').then((interception) => { - cy.get('.reviewFieldMappings').should('be.visible'); - cy.wait(5000); - const properties = interception.response.body.response.properties; - if (_.isEmpty(properties)) { - validatePendingFieldMappingsPanel(Object.entries(dns_mapping_fields)); - } else { - validateAutomaticFieldMappingsPanel(Object.entries(dns_mapping_fields)); - } - }); + validateFieldMappingsTable(); }); it('...can be deleted', () => { diff --git a/cypress/support/helpers.js b/cypress/support/helpers.js index 7c212a5bb..2033d9967 100644 --- a/cypress/support/helpers.js +++ b/cypress/support/helpers.js @@ -184,13 +184,13 @@ Cypress.Commands.add( .find('tbody') .find('tr') .then(($tr) => { - const length = Object.keys(data).length; + const length = data.length; length && cy.get($tr).should('have.length', length); cy.get($tr).within(($tr) => { data.map((rowData) => { - rowData.map((tdData) => { - cy.get($tr).find('td').contains(tdData); + rowData.forEach((tdData) => { + tdData && cy.get($tr).find('td').contains(`${tdData}`); }); }); }); diff --git a/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx b/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx index a7b2b5035..f704cb4e8 100644 --- a/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx +++ b/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx @@ -265,18 +265,20 @@ export default class ConfigureFieldMapping extends Component< initialIsOpen={false} > - - {...this.props} - loading={loading} - ruleFields={mappedRuleFields} - indexFields={indexFieldOptions} - mappingProps={{ - type: MappingViewType.Edit, - existingMappings, - invalidMappingFieldNames, - onMappingCreation: this.onMappingCreation, - }} - /> +
+ + {...this.props} + loading={loading} + ruleFields={mappedRuleFields} + indexFields={indexFieldOptions} + mappingProps={{ + type: MappingViewType.Edit, + existingMappings, + invalidMappingFieldNames, + onMappingCreation: this.onMappingCreation, + }} + /> +
@@ -306,18 +308,20 @@ export default class ConfigureFieldMapping extends Component<
Pending field mappings
- - {...this.props} - loading={loading} - ruleFields={unmappedRuleFields} - indexFields={indexFieldOptions} - mappingProps={{ - type: MappingViewType.Edit, - existingMappings, - invalidMappingFieldNames, - onMappingCreation: this.onMappingCreation, - }} - /> +
+ + {...this.props} + loading={loading} + ruleFields={unmappedRuleFields} + indexFields={indexFieldOptions} + mappingProps={{ + type: MappingViewType.Edit, + existingMappings, + invalidMappingFieldNames, + onMappingCreation: this.onMappingCreation, + }} + /> +
) : ( <> diff --git a/public/pages/Detectors/containers/FieldMappings/EditFieldMapping.tsx b/public/pages/Detectors/containers/FieldMappings/EditFieldMapping.tsx index c3fb44c31..263dfdb08 100644 --- a/public/pages/Detectors/containers/FieldMappings/EditFieldMapping.tsx +++ b/public/pages/Detectors/containers/FieldMappings/EditFieldMapping.tsx @@ -241,18 +241,20 @@ export default class EditFieldMappings extends Component< } > - - {...this.props} - loading={loading} - ruleFields={mappedRuleFields} - indexFields={logFieldOptions} - mappingProps={{ - type: MappingViewType.Edit, - existingMappings, - invalidMappingFieldNames, - onMappingCreation: this.onMappingCreation, - }} - /> +
+ + {...this.props} + loading={loading} + ruleFields={mappedRuleFields} + indexFields={logFieldOptions} + mappingProps={{ + type: MappingViewType.Edit, + existingMappings, + invalidMappingFieldNames, + onMappingCreation: this.onMappingCreation, + }} + /> +
@@ -272,18 +274,20 @@ export default class EditFieldMappings extends Component< - - {...this.props} - loading={loading} - ruleFields={unmappedRuleFields} - indexFields={logFieldOptions} - mappingProps={{ - type: MappingViewType.Edit, - existingMappings, - invalidMappingFieldNames, - onMappingCreation: this.onMappingCreation, - }} - /> +
+ + {...this.props} + loading={loading} + ruleFields={unmappedRuleFields} + indexFields={logFieldOptions} + mappingProps={{ + type: MappingViewType.Edit, + existingMappings, + invalidMappingFieldNames, + onMappingCreation: this.onMappingCreation, + }} + /> +
) : ( From c6de200d2d5ba0a76973edb1e2f2315849a6eabe Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Wed, 3 May 2023 21:44:51 +0200 Subject: [PATCH 64/64] cypress tests wait interval updated to 400 Signed-off-by: Jovan Cvetkovic --- .../EditFieldMappings.test.tsx.snap | 1260 +++++++++-------- 1 file changed, 632 insertions(+), 628 deletions(-) diff --git a/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap b/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap index c9551db91..e934dc1c0 100644 --- a/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap +++ b/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap @@ -293,277 +293,209 @@ exports[` spec renders the component 1`] = ` className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginXSmall" />
- + - -

- There are no field mappings. -

- - } - style={ - Object { - "maxWidth": "45em", - } - } - /> + ], + "id": "trigger_id_1", + "ids": Array [ + "rule_id_1", + ], + "name": "alert_name", + "sev_levels": Array [ + "severity_level_low", + ], + "severity": "1", + "tags": Array [ + "any.tag", + ], + "types": Array [ + "detector_type_1", + ], + }, + ], + "type": "detector", + } } - onTableChange={[Function]} - pagination={ - Object { - "pageIndex": 0, + fieldMappings={Array []} + filedMappingService={ + FieldMappingService { + "createMappings": [Function], + "getMappings": [Function], + "getMappingsView": [Function], + "httpClient": [MockFunction], } } - responsive={true} - sorting={ + indexFields={Array []} + loading={false} + mappingProps={ Object { - "sort": Object { - "direction": "asc", - "field": "ruleFieldName", - }, + "existingMappings": Object {}, + "invalidMappingFieldNames": Array [], + "onMappingCreation": [Function], + "type": 1, } } - tableLayout="fixed" + replaceFieldMappings={[MockFunction]} + ruleFields={Array []} > - spec renders the component 1`] = ` isSelectable={false} items={Array []} loading={false} - noItemsMessage={ + message={ @@ -617,106 +549,151 @@ exports[` spec renders the component 1`] = ` } /> } - onChange={[Function]} + onTableChange={[Function]} pagination={ Object { - "hidePerPageOptions": undefined, "pageIndex": 0, - "pageSize": 10, - "pageSizeOptions": Array [ - 10, - 25, - 50, - ], - "totalItemCount": 0, } } responsive={true} sorting={ Object { - "allowNeutralSort": true, "sort": Object { "direction": "asc", - "field": "Detector field name", + "field": "ruleFieldName", }, } } tableLayout="fixed" > -
+

+ There are no field mappings. +

+ + } + style={ + Object { + "maxWidth": "45em", + } + } + /> + } + onChange={[Function]} + pagination={ + Object { + "hidePerPageOptions": undefined, + "pageIndex": 0, + "pageSize": 10, + "pageSizeOptions": Array [ + 10, + 25, + 50, + ], + "totalItemCount": 0, + } + } + responsive={true} + sorting={ + Object { + "allowNeutralSort": true, + "sort": Object { + "direction": "asc", + "field": "Detector field name", + }, + } + } + tableLayout="fixed" > -
- -
- +
+ +
-
- -
- - -
- + + +
-
- - - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="none" +
-
-
+ spec renders the component 1`] = ` onClick={[Function]} size="xs" > - - + + + +
-
-
-
- -
-
-
- -
- - - + + + + + + + + + - - - - - - - - + + + + + - - - - + + - - Detector field name - - - - - - - - - + + - - - - + + - - Maps to - - - - - - - - - + + - - - - - - Log source field name - - - - - - - - - - - - - - - - - - + + - + + + + + + + + - - - - - -
- -
+ +
+ +
+ Detector field name + + + + + + + Maps to + + + + + + - -
-
- - -

- There are no field mappings. -

- - } - style={ - Object { - "maxWidth": "45em", - } - } + -
+ + + Status + + + + + + + + +
+
+ + +

+ There are no field mappings. +

+ + } style={ Object { "maxWidth": "45em", } } > - - - -
- -
-

- There are no field mappings. -

-
-
-
-
-
-
-
- - - -
-
+ + +
+ +
+

+ There are no field mappings. +

+
+
+
+
+
+ +
+ + +
+ + + + + + + + +
-
- - - + + + +