From 9139f296441d5de031af183290e4daceeb10159e Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Wed, 26 Jul 2023 19:31:54 -0700 Subject: [PATCH 1/5] log types table; details page with basic editing Signed-off-by: Amardeepsingh Siglani --- .../components/ContentPanel/ContentPanel.tsx | 7 +- public/models/interfaces.ts | 2 + .../LogTypes/components/LogTypeDetailsTab.tsx | 115 ++++++++++++ .../components/LogTypeDetectionRules.tsx | 10 + .../LogTypes/containers/LogTypeDetails.tsx | 137 ++++++++++++++ public/pages/LogTypes/containers/LogTypes.tsx | 61 ++++++ public/pages/LogTypes/utils/constants.ts | 15 ++ public/pages/LogTypes/utils/helpers.tsx | 52 ++++++ public/pages/Main/Main.tsx | 68 ++++--- public/security_analytics_app.tsx | 3 + public/services/LogTypeService.ts | 53 ++++++ public/services/index.ts | 2 + public/store/DataStore.ts | 4 + public/store/DetectorsStore.tsx | 17 -- public/store/LogTypeStore.ts | 70 +++++++ public/utils/constants.ts | 4 + server/clusters/addLogTypeMethods.ts | 52 ++++++ server/clusters/securityAnalyticsPlugin.ts | 2 + server/models/interfaces/index.ts | 3 + server/plugin.ts | 6 +- server/routes/LogTypeRoutes.ts | 59 ++++++ server/routes/index.ts | 2 + server/services/LogTypeService.ts | 176 ++++++++++++++++++ server/utils/constants.ts | 14 ++ test/mocks/services/browserHistory.mock.ts | 4 +- types/LogTypes.ts | 53 ++++++ types/index.ts | 1 + 27 files changed, 949 insertions(+), 43 deletions(-) create mode 100644 public/pages/LogTypes/components/LogTypeDetailsTab.tsx create mode 100644 public/pages/LogTypes/components/LogTypeDetectionRules.tsx create mode 100644 public/pages/LogTypes/containers/LogTypeDetails.tsx create mode 100644 public/pages/LogTypes/containers/LogTypes.tsx create mode 100644 public/pages/LogTypes/utils/constants.ts create mode 100644 public/pages/LogTypes/utils/helpers.tsx create mode 100644 public/services/LogTypeService.ts create mode 100644 public/store/LogTypeStore.ts create mode 100644 server/clusters/addLogTypeMethods.ts create mode 100644 server/routes/LogTypeRoutes.ts create mode 100644 server/services/LogTypeService.ts create mode 100644 types/LogTypes.ts diff --git a/public/components/ContentPanel/ContentPanel.tsx b/public/components/ContentPanel/ContentPanel.tsx index 93794ae95..57871bc65 100644 --- a/public/components/ContentPanel/ContentPanel.tsx +++ b/public/components/ContentPanel/ContentPanel.tsx @@ -11,6 +11,7 @@ import { EuiPanel, EuiTitle, EuiText, + EuiSpacer, } from '@elastic/eui'; interface ContentPanelProps { @@ -19,9 +20,9 @@ interface ContentPanelProps { subTitleText?: string | JSX.Element; bodyStyles?: object; panelStyles?: object; - horizontalRuleClassName?: string; actions?: React.ReactNode | React.ReactNode[]; children: React.ReactNode | React.ReactNode[]; + hideHeaderBorder?: boolean; className?: string; } @@ -43,9 +44,9 @@ const ContentPanel: React.SFC = ({ subTitleText = '', bodyStyles = {}, panelStyles = {}, - horizontalRuleClassName = '', actions, children, + hideHeaderBorder = false, className = '', }) => ( = ({ ) : null} - + {hideHeaderBorder ? : }
{children}
diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts index e47427ca7..b547fd486 100644 --- a/public/models/interfaces.ts +++ b/public/models/interfaces.ts @@ -14,6 +14,7 @@ import { RuleService, NotificationsService, IndexPatternsService, + LogTypeService, } from '../services'; import CorrelationService from '../services/CorrelationService'; @@ -29,6 +30,7 @@ export interface BrowserServices { notificationsService: NotificationsService; savedObjectsService: ISavedObjectsService; indexPatternsService: IndexPatternsService; + logTypeService: LogTypeService; } export interface RuleOptions { diff --git a/public/pages/LogTypes/components/LogTypeDetailsTab.tsx b/public/pages/LogTypes/components/LogTypeDetailsTab.tsx new file mode 100644 index 000000000..04504f35e --- /dev/null +++ b/public/pages/LogTypes/components/LogTypeDetailsTab.tsx @@ -0,0 +1,115 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiButton, + EuiDescriptionList, + EuiFormRow, + EuiFieldText, + EuiSpacer, + EuiTextArea, + EuiBottomBar, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, +} from '@elastic/eui'; +import { ContentPanel } from '../../../components/ContentPanel'; +import React from 'react'; +import { LogTypeItem } from '../../../../types'; +import { DataStore } from '../../../store/DataStore'; + +export interface LogTypeDetailsTabProps { + initialLogTypeDetails: LogTypeItem; + logTypeDetails: LogTypeItem; + isEditMode: boolean; + setIsEditMode: (isEdit: boolean) => void; + setLogTypeDetails: (logType: LogTypeItem) => void; +} + +export const LogTypeDetailsTab: React.FC = ({ + initialLogTypeDetails, + logTypeDetails, + isEditMode, + setIsEditMode, + setLogTypeDetails, +}) => { + const onUpdateLogType = async () => { + const success = await DataStore.logTypes.updateLogType(logTypeDetails); + if (success) { + setIsEditMode(false); + } + }; + + return ( + setIsEditMode(true)}>Edit]} + > + + + + setLogTypeDetails({ + ...logTypeDetails!, + name: e.target.value, + }) + } + placeholder="Enter name for log type" + disabled={!isEditMode || !!logTypeDetails.detectionRules} + /> + + + + + setLogTypeDetails({ + ...logTypeDetails!, + description: e.target.value, + }) + } + placeholder="Description of the log type" + disabled={!isEditMode} + /> + + {isEditMode ? ( + + + + { + setLogTypeDetails(initialLogTypeDetails); + setIsEditMode(false); + }} + > + Cancel + + + + + Update + + + + + ) : null} + + ), + }, + ]} + /> + + ); +}; diff --git a/public/pages/LogTypes/components/LogTypeDetectionRules.tsx b/public/pages/LogTypes/components/LogTypeDetectionRules.tsx new file mode 100644 index 000000000..765f03c37 --- /dev/null +++ b/public/pages/LogTypes/components/LogTypeDetectionRules.tsx @@ -0,0 +1,10 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface LogTypeDetectionRulesProps { + logTypeId: string; +} + +export const LogTypeDetectionRules = () => {}; diff --git a/public/pages/LogTypes/containers/LogTypeDetails.tsx b/public/pages/LogTypes/containers/LogTypeDetails.tsx new file mode 100644 index 000000000..8bff13635 --- /dev/null +++ b/public/pages/LogTypes/containers/LogTypeDetails.tsx @@ -0,0 +1,137 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useContext } from 'react'; +import { useState } from 'react'; +import { useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { LogTypeItem } from '../../../../types'; +import { + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPanel, + EuiSpacer, + EuiTab, + EuiTabs, + EuiTitle, +} from '@elastic/eui'; +import { DataStore } from '../../../store/DataStore'; +import { CoreServicesContext } from '../../../components/core_services'; +import { BREADCRUMBS } from '../../../utils/constants'; +import { logTypeDetailsTabs } from '../utils/constants'; +import { LogTypeDetailsTab } from '../components/LogTypeDetailsTab'; + +export interface LogTypeDetailsProps {} + +export const LogTypeDetails: React.FC = () => { + const context = useContext(CoreServicesContext); + const { logTypeId } = useParams<{ logTypeId: string }>(); + const [selectedTabId, setSelectedTabId] = useState('details'); + const [infoText, setInfoText] = useState( + <> + Loading details   + + + ); + const [logTypeDetails, setLogTypeDetails] = useState(undefined); + const [initialLogTypeDetails, setInitialLogTypeDetails] = useState( + undefined + ); + + const [isEditMode, setIsEditMode] = useState(false); + + useEffect(() => { + const getLogTypeDetails = async () => { + const details = await DataStore.logTypes.getLogType(logTypeId); + + if (!details) { + setInfoText('Log type not found!'); + return; + } + + setInitialLogTypeDetails(details); + setLogTypeDetails(details); + }; + + context?.chrome.setBreadcrumbs([ + BREADCRUMBS.SECURITY_ANALYTICS, + BREADCRUMBS.DETECTORS, + BREADCRUMBS.LOG_TYPES, + { text: logTypeId }, + ]); + getLogTypeDetails(); + }, []); + + const renderTabContent = () => { + switch (selectedTabId) { + case 'detection_rules': + return null; + case 'details': + default: + return ( + + ); + } + }; + + return !logTypeDetails ? ( + +

{infoText}

+
+ ) : ( + <> + +

{logTypeDetails.name}

+
+ + + + + + + + + + + + + + + + + + + {logTypeDetailsTabs.map((tab, index) => { + return ( + { + setSelectedTabId(tab.id); + }} + key={index} + isSelected={selectedTabId === tab.id} + > + {tab.name} + + ); + })} + + {renderTabContent()} + + ); +}; diff --git a/public/pages/LogTypes/containers/LogTypes.tsx b/public/pages/LogTypes/containers/LogTypes.tsx new file mode 100644 index 000000000..59a2a5f47 --- /dev/null +++ b/public/pages/LogTypes/containers/LogTypes.tsx @@ -0,0 +1,61 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useContext, useEffect, useState } from 'react'; +import { EuiInMemoryTable } from '@elastic/eui'; +import { ContentPanel } from '../../../components/ContentPanel'; +import { CoreServicesContext } from '../../../components/core_services'; +import { BREADCRUMBS, ROUTES } from '../../../utils/constants'; +import { LogType } from '../../../../types'; +import { DataStore } from '../../../store/DataStore'; +import { getLogTypesTableColumns } from '../utils/helpers'; +import { RouteComponentProps } from 'react-router-dom'; +import { useCallback } from 'react'; + +export interface LogTypesProps extends RouteComponentProps {} + +export const LogTypes: React.FC = ({ history }) => { + const context = useContext(CoreServicesContext); + const [logTypes, setLogTypes] = useState([]); + + useEffect(() => { + context?.chrome.setBreadcrumbs([ + BREADCRUMBS.SECURITY_ANALYTICS, + BREADCRUMBS.DETECTORS, + BREADCRUMBS.LOG_TYPES, + ]); + const getLogTypes = async () => { + const logTypes = await DataStore.logTypes.getLogTypes(); + setLogTypes(logTypes); + }; + + getLogTypes(); + }, []); + + const showLogTypeDetails = useCallback((id: string) => { + history.push(`${ROUTES.LOG_TYPES}/${id}`); + }, []); + + return ( + + + alert(`Deleted ${id}`) + )} + pagination={{ + initialPageSize: 25, + }} + sorting={true} + /> + + ); +}; diff --git a/public/pages/LogTypes/utils/constants.ts b/public/pages/LogTypes/utils/constants.ts new file mode 100644 index 000000000..f7a7837f3 --- /dev/null +++ b/public/pages/LogTypes/utils/constants.ts @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const logTypeDetailsTabs = [ + { + id: 'details', + name: 'Details', + }, + { + id: 'detection_rules', + name: 'Detection rules', + }, +]; diff --git a/public/pages/LogTypes/utils/helpers.tsx b/public/pages/LogTypes/utils/helpers.tsx new file mode 100644 index 000000000..2ad78d793 --- /dev/null +++ b/public/pages/LogTypes/utils/helpers.tsx @@ -0,0 +1,52 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiButtonIcon, EuiLink, EuiToolTip } from '@elastic/eui'; +import { LogType } from '../../../../types'; +import { capitalize } from 'lodash'; + +export const getLogTypesTableColumns = ( + showDetails: (id: string) => void, + deleteLogType: (logTypeId: string) => void +) => [ + { + field: 'name', + name: 'Name', + sortable: true, + render: (name: string, item: LogType) => { + return showDetails(item.id)}>{name}; + }, + }, + { + field: 'description', + name: 'Description', + truncateText: false, + }, + { + field: 'source', + name: 'Source', + render: (source: string) => capitalize(source), + }, + { + name: 'Actions', + actions: [ + { + render: (item: LogType) => { + return ( + + deleteLogType(item.id)} + /> + + ); + }, + }, + ], + }, +]; diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx index d1c3fce60..aab5ec1ca 100644 --- a/public/pages/Main/Main.tsx +++ b/public/pages/Main/Main.tsx @@ -47,6 +47,8 @@ import { Correlations } from '../Correlations/containers/CorrelationsContainer'; import FindingDetailsFlyout, { FindingDetailsFlyoutBaseProps, } from '../Findings/components/FindingDetailsFlyout'; +import { LogTypes } from '../LogTypes/containers/LogTypes'; +import { LogTypeDetails } from '../LogTypes/containers/LogTypeDetails'; enum Navigation { SecurityAnalytics = 'Security Analytics', @@ -57,6 +59,7 @@ enum Navigation { Alerts = 'Alerts', Correlations = 'Correlations', CorrelationRules = 'Correlation rules', + LogTypes = 'Log types', } /** @@ -72,6 +75,7 @@ const HIDDEN_NAV_ROUTES: string[] = [ ROUTES.EDIT_DETECTOR_RULES, ROUTES.EDIT_FIELD_MAPPINGS, ROUTES.EDIT_DETECTOR_ALERT_TRIGGERS, + `${ROUTES.LOG_TYPES}/.+`, ]; interface MainProps extends RouteComponentProps { @@ -80,7 +84,7 @@ interface MainProps extends RouteComponentProps { interface MainState { getStartedDismissedOnce: boolean; - selectedNavItemIndex: number; + selectedNavItemId: number; dateTimeFilter: DateTimeFilter; callout?: ICalloutProps; toasts?: Toast[]; @@ -100,7 +104,7 @@ export default class Main extends Component { super(props); this.state = { getStartedDismissedOnce: false, - selectedNavItemIndex: 1, + selectedNavItemId: 1, dateTimeFilter: { startTime: DEFAULT_DATE_RANGE.start, endTime: DEFAULT_DATE_RANGE.end, @@ -172,11 +176,11 @@ export default class Main extends Component { updateSelectedNavItem() { const routeIndex = this.getCurrentRouteIndex(); if (routeIndex) { - this.setState({ selectedNavItemIndex: routeIndex }); + this.setState({ selectedNavItemId: routeIndex }); } if (this.props.location.pathname.includes('detector-details')) { - this.setState({ selectedNavItemIndex: navItemIndexByRoute[ROUTES.DETECTORS] }); + this.setState({ selectedNavItemId: navItemIndexByRoute[ROUTES.DETECTORS] }); } } @@ -191,7 +195,7 @@ export default class Main extends Component { history, } = this.props; - const { callout, findingFlyout } = this.state; + const { callout, findingFlyout, selectedNavItemId: selectedNavItemIndex } = this.state; const sideNav: EuiSideNavItemType<{ style: any }>[] = [ { name: Navigation.SecurityAnalytics, @@ -211,52 +215,64 @@ export default class Main extends Component { name: Navigation.Overview, id: 1, onClick: () => { - this.setState({ selectedNavItemIndex: 1 }); + this.setState({ selectedNavItemId: 1 }); history.push(ROUTES.OVERVIEW); }, - isSelected: this.state.selectedNavItemIndex === 1, + isSelected: selectedNavItemIndex === 1, }, { name: Navigation.Findings, id: 2, onClick: () => { - this.setState({ selectedNavItemIndex: 2 }); + this.setState({ selectedNavItemId: 2 }); history.push(ROUTES.FINDINGS); }, - isSelected: this.state.selectedNavItemIndex === 2, + isSelected: selectedNavItemIndex === 2, }, { name: Navigation.Alerts, id: 3, onClick: () => { - this.setState({ selectedNavItemIndex: 3 }); + this.setState({ selectedNavItemId: 3 }); history.push(ROUTES.ALERTS); }, - isSelected: this.state.selectedNavItemIndex === 3, + isSelected: selectedNavItemIndex === 3, }, { name: Navigation.Detectors, id: 4, onClick: () => { - this.setState({ selectedNavItemIndex: 4 }); + this.setState({ selectedNavItemId: 4 }); history.push(ROUTES.DETECTORS); }, - isSelected: this.state.selectedNavItemIndex === 4, + forceOpen: true, + isSelected: selectedNavItemIndex === 4, + items: [ + { + name: Navigation.LogTypes, + id: 8, + onClick: () => { + this.setState({ selectedNavItemId: 8 }); + history.push(ROUTES.LOG_TYPES); + }, + isSelected: selectedNavItemIndex === 8, + }, + ], }, { name: Navigation.Rules, id: 5, onClick: () => { - this.setState({ selectedNavItemIndex: 5 }); + this.setState({ selectedNavItemId: 5 }); history.push(ROUTES.RULES); }, - isSelected: this.state.selectedNavItemIndex === 5, + isSelected: selectedNavItemIndex === 5, }, { name: Navigation.Correlations, id: 6, onClick: () => { - this.setState({ selectedNavItemIndex: 6 }); + this.setState({ selectedNavItemId: 6 }); history.push(ROUTES.CORRELATIONS); }, renderItem: (props) => { @@ -266,7 +282,7 @@ export default class Main extends Component { { - this.setState({ selectedNavItemIndex: 6 }); + this.setState({ selectedNavItemId: 6 }); history.push(ROUTES.CORRELATIONS); }} > @@ -276,17 +292,17 @@ export default class Main extends Component { ); }, - isSelected: this.state.selectedNavItemIndex === 6, + isSelected: selectedNavItemIndex === 6, forceOpen: true, items: [ { name: Navigation.CorrelationRules, id: 7, onClick: () => { - this.setState({ selectedNavItemIndex: 7 }); + this.setState({ selectedNavItemId: 7 }); history.push(ROUTES.CORRELATION_RULES); }, - isSelected: this.state.selectedNavItemIndex === 7, + isSelected: selectedNavItemIndex === 7, }, ], }, @@ -534,13 +550,23 @@ export default class Main extends Component { this.setState({ selectedNavItemIndex: 6 })} + onMount={() => this.setState({ selectedNavItemId: 6 })} dateTimeFilter={this.state.dateTimeFilter} setDateTimeFilter={this.setDateTimeFilter} /> ); }} /> + } + /> + ) => { + return ; + }} + /> diff --git a/public/security_analytics_app.tsx b/public/security_analytics_app.tsx index 5bae1455b..f6fb44292 100644 --- a/public/security_analytics_app.tsx +++ b/public/security_analytics_app.tsx @@ -12,6 +12,7 @@ import { NotificationsService, ServicesContext, IndexPatternsService, + LogTypeService, } from './services'; import { DarkModeContext } from './components/DarkMode'; import Main from './pages/Main'; @@ -48,6 +49,7 @@ export function renderApp( const notificationsService = new NotificationsService(http); const savedObjectsService = new SavedObjectService(savedObjects.client, indexService); const indexPatternsService = new IndexPatternsService(depsStart.data.indexPatterns); + const logTypeService = new LogTypeService(http); const services: BrowserServices = { detectorsService, @@ -61,6 +63,7 @@ export function renderApp( notificationsService, savedObjectsService, indexPatternsService, + logTypeService, }; const isDarkMode = coreStart.uiSettings.get('theme:darkMode') || false; diff --git a/public/services/LogTypeService.ts b/public/services/LogTypeService.ts new file mode 100644 index 000000000..7ab3213f4 --- /dev/null +++ b/public/services/LogTypeService.ts @@ -0,0 +1,53 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { HttpSetup } from 'opensearch-dashboards/public'; +import { + CreateLogTypeResponse, + DeleteLogTypeResponse, + LogTypeBase, + SearchLogTypesResponse, + ServerResponse, + UpdateLogTypeResponse, +} from '../../types'; +import { API } from '../../server/utils/constants'; + +export default class LogTypeService { + constructor(private httpClient: HttpSetup) {} + + createLogType = async (logType: LogTypeBase) => { + const url = `..${API.LOGTYPE_BASE}`; + const response = (await this.httpClient.post(url, { + body: JSON.stringify(logType), + })) as ServerResponse; + + return response; + }; + + searchLogTypes = async (id?: string): Promise> => { + const url = `..${API.LOGTYPE_BASE}/_search`; + const query = id ? JSON.stringify({ terms: { _id: [id] } }) : undefined; + return (await this.httpClient.post(url, { body: query })) as ServerResponse< + SearchLogTypesResponse + >; + }; + + updateLogType = async ( + logTypeId: string, + logType: LogTypeBase + ): Promise> => { + const url = `..${API.LOGTYPE_BASE}/${logTypeId}`; + const response = (await this.httpClient.put(url, { + body: JSON.stringify(logType), + })) as ServerResponse; + + return response; + }; + + deleteLogType = async (logTypeId: string): Promise> => { + const url = `..${API.LOGTYPE_BASE}/${logTypeId}`; + return (await this.httpClient.delete(url)) as ServerResponse; + }; +} diff --git a/public/services/index.ts b/public/services/index.ts index c708b4b15..8949bb722 100644 --- a/public/services/index.ts +++ b/public/services/index.ts @@ -15,6 +15,7 @@ import NotificationsService from './NotificationsService'; import IndexPatternsService from './IndexPatternsService'; import SavedObjectService from './SavedObjectService'; import CorrelationService from './CorrelationService'; +import LogTypeService from './LogTypeService'; export { ServicesConsumer, @@ -30,4 +31,5 @@ export { NotificationsService, IndexPatternsService, SavedObjectService, + LogTypeService, }; diff --git a/public/store/DataStore.ts b/public/store/DataStore.ts index 4f085d1e7..6c179621b 100644 --- a/public/store/DataStore.ts +++ b/public/store/DataStore.ts @@ -9,12 +9,14 @@ import { NotificationsStart } from 'opensearch-dashboards/public'; import { DetectorsStore } from './DetectorsStore'; import { CorrelationsStore } from './CorrelationsStore'; import { FindingsStore } from './FindingsStore'; +import { LogTypeStore } from './LogTypeStore'; export class DataStore { public static rules: RulesStore; public static detectors: DetectorsStore; public static correlations: CorrelationsStore; public static findings: FindingsStore; + public static logTypes: LogTypeStore; public static init = (services: BrowserServices, notifications: NotificationsStart) => { const rulesStore = new RulesStore(services.ruleService, notifications); @@ -39,5 +41,7 @@ export class DataStore { notifications, rulesStore ); + + DataStore.logTypes = new LogTypeStore(services.logTypeService, notifications); }; } diff --git a/public/store/DetectorsStore.tsx b/public/store/DetectorsStore.tsx index 40c388f7f..e292a8b61 100644 --- a/public/store/DetectorsStore.tsx +++ b/public/store/DetectorsStore.tsx @@ -37,8 +37,6 @@ export interface IDetectorsStore { ) => void; } -export interface IDetectorsCache {} - export interface IDetectorsState { pendingRequests: Promise[]; detectorInput: CreateDetectorState; @@ -83,13 +81,6 @@ export class DetectorsStore implements IDetectorsStore { */ history: RouteComponentProps['history'] | undefined = undefined; - /** - * Keeps detector's data cached - * - * @property {IDetectorsCache} cache - */ - private cache: IDetectorsCache = {}; - /** * Store state * @private {IDetectorsState} @@ -112,14 +103,6 @@ export class DetectorsStore implements IDetectorsStore { this.savedObjectsService = savedObjectsService; } - /** - * Invalidates all detectors data - */ - private invalidateCache = (): DetectorsStore => { - this.cache = {}; - return this; - }; - public setState = (state: IDetectorsState, history: RouteComponentProps['history']): void => { this.state = state; this.history = history; diff --git a/public/store/LogTypeStore.ts b/public/store/LogTypeStore.ts new file mode 100644 index 000000000..8d8eaa095 --- /dev/null +++ b/public/store/LogTypeStore.ts @@ -0,0 +1,70 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { NotificationsStart } from 'opensearch-dashboards/public'; +import { LogType, LogTypeBase, LogTypeItem } from '../../types'; +import LogTypeService from '../services/LogTypeService'; +import { errorNotificationToast } from '../utils/helpers'; + +export class LogTypeStore { + constructor(private service: LogTypeService, private notifications: NotificationsStart) {} + + public async getLogType(id: string): Promise { + const logTypesRes = await this.service.searchLogTypes(id); + if (logTypesRes.ok) { + const logTypes: LogType[] = logTypesRes.response.hits.hits.map((hit) => { + return { + id: hit._id, + ...hit._source, + }; + }); + + return { ...logTypes[0], detectionRules: 10 }; + } + + return undefined; + } + + public async getLogTypes(): Promise { + const logTypesRes = await this.service.searchLogTypes(); + if (logTypesRes.ok) { + const logTypes: LogType[] = logTypesRes.response.hits.hits.map((hit) => { + return { + id: hit._id, + ...hit._source, + }; + }); + + return logTypes; + } + + return []; + } + + public async createLogType(logType: LogTypeBase): Promise { + const createRes = await this.service.createLogType(logType); + + if (!createRes.ok) { + errorNotificationToast(this.notifications, 'create', 'log type', createRes.error); + } + + return createRes.ok; + } + + public async updateLogType({ id, name, description, source, tags }: LogType): Promise { + const updateRes = await this.service.updateLogType(id, { + name, + description, + source, + tags, + }); + + if (!updateRes.ok) { + errorNotificationToast(this.notifications, 'update', 'log type', updateRes.error); + } + + return updateRes.ok; + } +} diff --git a/public/utils/constants.ts b/public/utils/constants.ts index fea6c072a..20f9a026c 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -44,6 +44,8 @@ export const ROUTES = Object.freeze({ CORRELATION_RULES: '/correlations/rules', CORRELATION_RULE_CREATE: '/correlations/create-rule', CORRELATION_RULE_EDIT: '/correlations/rule', + LOG_TYPES: '/log-types', + LOG_TYPES_CREATE: '/create-log-type', get LANDING_PAGE(): string { return this.OVERVIEW; @@ -80,6 +82,8 @@ export const BREADCRUMBS = Object.freeze({ text: 'Create correlation rule', href: `#${ROUTES.CORRELATION_RULE_CREATE}`, }, + LOG_TYPES: { text: 'Log types', href: `#${ROUTES.LOG_TYPES}` }, + LOG_TYPE_CREATE: { text: 'Create log type', href: `#${ROUTES.LOG_TYPES_CREATE}` }, }); export enum SortDirection { diff --git a/server/clusters/addLogTypeMethods.ts b/server/clusters/addLogTypeMethods.ts new file mode 100644 index 000000000..6a80dbe1e --- /dev/null +++ b/server/clusters/addLogTypeMethods.ts @@ -0,0 +1,52 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { API, METHOD_NAMES } from '../utils/constants'; + +export function addLogTypeMethods(securityAnalytics: any, createAction: any): void { + securityAnalytics[METHOD_NAMES.SEARCH_LOGTYPES] = createAction({ + url: { + fmt: `${API.LOGTYPE_BASE}/_search`, + }, + needBody: true, + method: 'POST', + }); + + securityAnalytics[METHOD_NAMES.CREATE_LOGTYPE] = createAction({ + url: { + fmt: `${API.LOGTYPE_BASE}`, + }, + needBody: true, + method: 'POST', + }); + + securityAnalytics[METHOD_NAMES.UPDATE_LOGTYPE] = createAction({ + url: { + fmt: `${API.LOGTYPE_BASE}/<%=logTypeId%>`, + req: { + logTypeId: { + type: 'string', + required: true, + }, + }, + }, + needBody: true, + method: 'PUT', + }); + + securityAnalytics[METHOD_NAMES.DELETE_LOGTYPE] = createAction({ + url: { + fmt: `${API.LOGTYPE_BASE}/<%=logTypeId%>`, + req: { + logTypeId: { + type: 'string', + required: true, + }, + }, + }, + needBody: false, + method: 'DELETE', + }); +} diff --git a/server/clusters/securityAnalyticsPlugin.ts b/server/clusters/securityAnalyticsPlugin.ts index 76095b2cb..f400b18f4 100644 --- a/server/clusters/securityAnalyticsPlugin.ts +++ b/server/clusters/securityAnalyticsPlugin.ts @@ -11,6 +11,7 @@ import { addFindingsMethods } from './addFindingsMethods'; import { addRulesMethods } from './addRuleMethods'; import { addNotificationsMethods } from './addNotificationsMethods'; import { addCorrelationMethods } from './addCorrelationMethods'; +import { addLogTypeMethods } from './addLogTypeMethods'; export function securityAnalyticsPlugin(Client: any, config: any, components: any) { const createAction = components.clientAction.factory; @@ -25,4 +26,5 @@ export function securityAnalyticsPlugin(Client: any, config: any, components: an addAlertsMethods(securityAnalytics, createAction); addRulesMethods(securityAnalytics, createAction); addNotificationsMethods(securityAnalytics, createAction); + addLogTypeMethods(securityAnalytics, createAction); } diff --git a/server/models/interfaces/index.ts b/server/models/interfaces/index.ts index c206c8269..b5b4dd93f 100644 --- a/server/models/interfaces/index.ts +++ b/server/models/interfaces/index.ts @@ -13,6 +13,7 @@ import { CorrelationService, } from '../../services'; import AlertService from '../../services/AlertService'; +import { LogTypeService } from '../../services/LogTypeService'; import RulesService from '../../services/RuleService'; export interface SecurityAnalyticsApi { @@ -33,6 +34,7 @@ export interface SecurityAnalyticsApi { readonly ACKNOWLEDGE_ALERTS: string; readonly UPDATE_ALIASES: string; readonly CORRELATIONS: string; + readonly LOGTYPE_BASE: string; } export interface NodeServices { @@ -45,6 +47,7 @@ export interface NodeServices { alertService: AlertService; rulesService: RulesService; notificationsService: NotificationsService; + logTypeService: LogTypeService; } export interface GetIndicesResponse { diff --git a/server/plugin.ts b/server/plugin.ts index ffc258070..44738948a 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -16,8 +16,9 @@ import { setupIndexRoutes, setupAlertsRoutes, setupNotificationsRoutes, + setupLogTypeRoutes, + setupRulesRoutes, } from './routes'; -import { setupRulesRoutes } from './routes/RuleRoutes'; import { IndexService, FindingsService, @@ -29,6 +30,7 @@ import { NotificationsService, CorrelationService, } from './services'; +import { LogTypeService } from './services/LogTypeService'; export class SecurityAnalyticsPlugin implements Plugin { @@ -47,6 +49,7 @@ export class SecurityAnalyticsPlugin alertService: new AlertService(osDriver), rulesService: new RulesService(osDriver), notificationsService: new NotificationsService(osDriver), + logTypeService: new LogTypeService(osDriver), }; // Create router @@ -62,6 +65,7 @@ export class SecurityAnalyticsPlugin setupAlertsRoutes(services, router); setupRulesRoutes(services, router); setupNotificationsRoutes(services, router); + setupLogTypeRoutes(services, router); return {}; } diff --git a/server/routes/LogTypeRoutes.ts b/server/routes/LogTypeRoutes.ts new file mode 100644 index 000000000..94396a089 --- /dev/null +++ b/server/routes/LogTypeRoutes.ts @@ -0,0 +1,59 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IRouter } from 'opensearch-dashboards/server'; +import { schema } from '@osd/config-schema'; +import { NodeServices } from '../models/interfaces'; +import { API } from '../utils/constants'; + +export function setupLogTypeRoutes(services: NodeServices, router: IRouter) { + const { logTypeService } = services; + + router.post( + { + path: API.LOGTYPE_BASE, + validate: { + body: schema.any(), + }, + }, + logTypeService.createLogType + ); + + router.post( + { + path: `${API.LOGTYPE_BASE}/_search`, + validate: { + body: schema.any(), + }, + }, + logTypeService.searchLogTypes + ); + + router.put( + { + path: `${API.LOGTYPE_BASE}/{logTypeId}`, + validate: { + params: schema.object({ + logTypeId: schema.string(), + }), + body: schema.any(), + }, + }, + logTypeService.updateLogType + ); + + router.delete( + { + path: `${API.LOGTYPE_BASE}/{logTypeId}`, + validate: { + params: schema.object({ + logTypeId: schema.string(), + }), + body: schema.any(), + }, + }, + logTypeService.deleteLogType + ); +} diff --git a/server/routes/index.ts b/server/routes/index.ts index 9b191e3fa..cff7aad92 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -11,3 +11,5 @@ export * from './FieldMappingRoutes'; export * from './IndexRoutes'; export * from './AlertRoutes'; export * from './NotificationsRoutes'; +export * from './LogTypeRoutes'; +export * from './RuleRoutes'; diff --git a/server/services/LogTypeService.ts b/server/services/LogTypeService.ts new file mode 100644 index 000000000..159624a6c --- /dev/null +++ b/server/services/LogTypeService.ts @@ -0,0 +1,176 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + ILegacyCustomClusterClient, + IOpenSearchDashboardsResponse, + OpenSearchDashboardsRequest, + OpenSearchDashboardsResponseFactory, + RequestHandlerContext, + ResponseError, +} from 'opensearch-dashboards/server'; +import { ServerResponse } from '../models/types'; +import { + CreateLogTypeRequestBody, + CreateLogTypeResponse, + DeleteLogTypeParams, + DeleteLogTypeResponse, + LogTypeBase, + SearchLogTypesResponse, + UpdateLogTypeParams, + UpdateLogTypeResponse, +} from '../../types'; +import { CLIENT_LOGTYPE_METHODS } from '../utils/constants'; + +export class LogTypeService { + constructor(private osDriver: ILegacyCustomClusterClient) {} + + createLogType = async ( + _context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory + ): Promise< + IOpenSearchDashboardsResponse | ResponseError> + > => { + try { + const logType = request.body; + const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); + const createLogTypeResponse: CreateLogTypeResponse = await callWithRequest( + CLIENT_LOGTYPE_METHODS.CREATE_LOGTYPE, + { body: logType } + ); + + return response.custom({ + statusCode: 200, + body: { + ok: true, + response: createLogTypeResponse, + }, + }); + } catch (error: any) { + console.error('Security Analytics - LogTypeService - createLogType:', error); + return response.custom({ + statusCode: 200, + body: { + ok: false, + error: error.message, + }, + }); + } + }; + + searchLogTypes = async ( + _context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory + ): Promise< + IOpenSearchDashboardsResponse | ResponseError> + > => { + try { + const query = request.body; + const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); + const searchLogTypesResponse: SearchLogTypesResponse = await callWithRequest( + CLIENT_LOGTYPE_METHODS.SEARCH_LOGTYPES, + { + body: { + size: 10000, + query: query ?? { + match_all: {}, + }, + }, + } + ); + + return response.custom({ + statusCode: 200, + body: { + ok: true, + response: searchLogTypesResponse, + }, + }); + } catch (error: any) { + console.error('Security Analytics - LogTypeService - searchLogTypes:', error); + return response.custom({ + statusCode: 200, + body: { + ok: false, + error: error.message, + }, + }); + } + }; + + updateLogType = async ( + _context: RequestHandlerContext, + request: OpenSearchDashboardsRequest<{ logTypeId: string }, unknown, LogTypeBase>, + response: OpenSearchDashboardsResponseFactory + ): Promise< + IOpenSearchDashboardsResponse | ResponseError> + > => { + try { + const logType = request.body; + const { logTypeId } = request.params; + const params: UpdateLogTypeParams = { body: logType, logTypeId }; + const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); + console.log(JSON.stringify(params)); + const updateLogTypeResponse: UpdateLogTypeResponse = await callWithRequest( + CLIENT_LOGTYPE_METHODS.UPDATE_LOGTYPE, + params + ); + + return response.custom({ + statusCode: 200, + body: { + ok: true, + response: updateLogTypeResponse, + }, + }); + } catch (error: any) { + console.error('Security Analytics - LogTypeService - updateLogType:', error); + return response.custom({ + statusCode: 200, + body: { + ok: false, + error: error.message, + }, + }); + } + }; + + deleteLogType = async ( + _context: RequestHandlerContext, + request: OpenSearchDashboardsRequest<{ logTypeId: string }>, + response: OpenSearchDashboardsResponseFactory + ): Promise< + IOpenSearchDashboardsResponse | ResponseError> + > => { + try { + const { logTypeId } = request.params; + const params: DeleteLogTypeParams = { logTypeId }; + const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); + const deleteLogTypeResponse: DeleteLogTypeResponse = await callWithRequest( + CLIENT_LOGTYPE_METHODS.DELETE_LOGTYPE, + params + ); + + return response.custom({ + statusCode: 200, + body: { + ok: true, + response: deleteLogTypeResponse, + }, + }); + } catch (error: any) { + console.error('Security Analytics - DetectorsService - deleteDetector:', error); + return response.custom({ + statusCode: 200, + body: { + ok: false, + error: error.message, + }, + }); + } + }; +} diff --git a/server/utils/constants.ts b/server/utils/constants.ts index 9275dae9d..0d5e8a83d 100644 --- a/server/utils/constants.ts +++ b/server/utils/constants.ts @@ -31,6 +31,7 @@ export const API: SecurityAnalyticsApi = { ACKNOWLEDGE_ALERTS: `${BASE_API_PATH}/detectors/{detector_id}/_acknowledge/alerts`, UPDATE_ALIASES: `${BASE_API_PATH}/update_aliases`, CORRELATIONS: `${BASE_API_PATH}/correlations`, + LOGTYPE_BASE: `${BASE_API_PATH}/logtype`, }; /** @@ -77,6 +78,12 @@ export const METHOD_NAMES = { // Notifications methods GET_CHANNEl: 'getChannel', GET_CHANNElS: 'getChannels', + + // LogType methods + SEARCH_LOGTYPES: 'searchLogTypes', + CREATE_LOGTYPE: 'createLogType', + UPDATE_LOGTYPE: 'updateLogType', + DELETE_LOGTYPE: 'deleteLogType', }; /** @@ -125,3 +132,10 @@ export const CLIENT_NOTIFICATIONS_METHODS = { GET_CHANNEL: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.GET_CHANNEl}`, GET_CHANNELS: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.GET_CHANNElS}`, }; + +export const CLIENT_LOGTYPE_METHODS = { + SEARCH_LOGTYPES: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.SEARCH_LOGTYPES}`, + CREATE_LOGTYPE: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.CREATE_LOGTYPE}`, + UPDATE_LOGTYPE: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.UPDATE_LOGTYPE}`, + DELETE_LOGTYPE: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.DELETE_LOGTYPE}`, +}; diff --git a/test/mocks/services/browserHistory.mock.ts b/test/mocks/services/browserHistory.mock.ts index 9af44b50c..69e89927a 100644 --- a/test/mocks/services/browserHistory.mock.ts +++ b/test/mocks/services/browserHistory.mock.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { RouteComponentProps } from 'react-router-dom'; + export default ({ replace: jest.fn(), listen: jest.fn(), @@ -10,4 +12,4 @@ export default ({ pathname: '', }, push: jest.fn(), -} as unknown) as History; +} as unknown) as RouteComponentProps['history']; diff --git a/types/LogTypes.ts b/types/LogTypes.ts new file mode 100644 index 000000000..229c36b19 --- /dev/null +++ b/types/LogTypes.ts @@ -0,0 +1,53 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface LogTypeItem extends LogType { + detectionRules: number; +} + +export interface LogType extends LogTypeBase { + id: string; +} + +export interface LogTypeBase { + name: string; + description: string; + source: string; + tags: { + correlation_id: number; + } | null; +} + +export interface SearchLogTypesResponse { + hits: { + hits: { + _id: string; + _source: LogTypeBase; + }[]; + }; +} + +export interface CreateLogTypeRequestBody extends LogTypeBase {} + +export interface CreateLogTypeResponse { + _id: string; + logType: LogTypeBase; +} + +export interface UpdateLogTypeParams { + logTypeId: string; + body: LogTypeBase; +} + +export interface UpdateLogTypeResponse { + _id: string; + logType: LogTypeBase; +} + +export interface DeleteLogTypeParams { + logTypeId: string; +} + +export interface DeleteLogTypeResponse {} diff --git a/types/index.ts b/types/index.ts index b02e630f8..229873194 100644 --- a/types/index.ts +++ b/types/index.ts @@ -14,3 +14,4 @@ export * from './Rule'; export * from './services'; export * from './SavedObjectConfig'; export * from './Correlations'; +export * from './LogTypes'; From ad1bb6da3062b5a51933a2a0f5f2d44f2b7fb4ff Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Fri, 28 Jul 2023 11:28:05 -0700 Subject: [PATCH 2/5] updated snapshots Signed-off-by: Amardeepsingh Siglani --- .../Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap | 1 - .../__snapshots__/DetectorRulesView.test.tsx.snap | 1 - .../__snapshots__/UpdateDetectorBasicDetails.test.tsx.snap | 2 -- .../__snapshots__/AlertTriggersView.test.tsx.snap | 1 - .../Detector/__snapshots__/DetectorDetails.test.tsx.snap | 2 -- .../__snapshots__/DetectorDetailsView.test.tsx.snap | 2 -- 6 files changed, 9 deletions(-) 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 ea1294d09..fbe716138 100644 --- a/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap +++ b/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap @@ -1109,7 +1109,6 @@ exports[` spec renders the component 1`] = `
spec renders the component 1`] = `
spec renders the component 1`] = `
spec renders the component 1`] = `
spec renders the component 1`] = `
spec renders the component 1`] = `
spec renders the component 1`] = `
spec renders the component 1`] = `
spec renders the component 1`] = `
Date: Fri, 28 Jul 2023 11:32:23 -0700 Subject: [PATCH 3/5] addressed PR comments Signed-off-by: Amardeepsingh Siglani --- public/store/LogTypeStore.ts | 7 ++++++- server/services/LogTypeService.ts | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/store/LogTypeStore.ts b/public/store/LogTypeStore.ts index 8d8eaa095..1eebdc8ae 100644 --- a/public/store/LogTypeStore.ts +++ b/public/store/LogTypeStore.ts @@ -7,6 +7,7 @@ import { NotificationsStart } from 'opensearch-dashboards/public'; import { LogType, LogTypeBase, LogTypeItem } from '../../types'; import LogTypeService from '../services/LogTypeService'; import { errorNotificationToast } from '../utils/helpers'; +import { DataStore } from './DataStore'; export class LogTypeStore { constructor(private service: LogTypeService, private notifications: NotificationsStart) {} @@ -21,7 +22,11 @@ export class LogTypeStore { }; }); - return { ...logTypes[0], detectionRules: 10 }; + const rulesRes = await DataStore.rules.getAllRules({ + 'rule.category': [id], + }); + + return { ...logTypes[0], detectionRules: rulesRes.length }; } return undefined; diff --git a/server/services/LogTypeService.ts b/server/services/LogTypeService.ts index 159624a6c..1e190d73f 100644 --- a/server/services/LogTypeService.ts +++ b/server/services/LogTypeService.ts @@ -114,7 +114,6 @@ export class LogTypeService { const { logTypeId } = request.params; const params: UpdateLogTypeParams = { body: logType, logTypeId }; const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); - console.log(JSON.stringify(params)); const updateLogTypeResponse: UpdateLogTypeResponse = await callWithRequest( CLIENT_LOGTYPE_METHODS.UPDATE_LOGTYPE, params From 30df5c8625e3d1284675305271f1bc884bb7dd14 Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Fri, 28 Jul 2023 11:49:33 -0700 Subject: [PATCH 4/5] updated cypress workflow Signed-off-by: Amardeepsingh Siglani --- .github/workflows/cypress-workflow.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cypress-workflow.yml b/.github/workflows/cypress-workflow.yml index 841e81cbc..5df3d7107 100644 --- a/.github/workflows/cypress-workflow.yml +++ b/.github/workflows/cypress-workflow.yml @@ -7,14 +7,14 @@ on: branches: - "*" env: - OPENSEARCH_DASHBOARDS_VERSION: '2.8.0' - OPENSEARCH_VERSION: '2.8.0-SNAPSHOT' + OPENSEARCH_DASHBOARDS_VERSION: '2.9.0' + OPENSEARCH_VERSION: '2.9.0-SNAPSHOT' SECURITY_ANALYTICS_BRANCH: '2.x' GRADLE_VERSION: '7.6.1' # If this variable is not empty, the package.json, opensearch_dashboards.json, and yarn.lock files will be replaced # with those files from the 'opensearch-project/security-analytics-dashboards-plugin' branch or commit specified. - OVERRIDE_REFERENCE: '2.8' + OVERRIDE_REFERENCE: '2.9' jobs: tests: name: Run Cypress E2E tests From 9c4a113743340349fd89c67d054a8f012a75acde Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Fri, 28 Jul 2023 15:08:51 -0700 Subject: [PATCH 5/5] updated detector cypress test Signed-off-by: Amardeepsingh Siglani --- cypress/integration/1_detectors.spec.js | 35 +++---------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index f9e111f81..dda870d3d 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -48,9 +48,10 @@ const validateFieldMappingsTable = (message = '') => { 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.map( - (field) => [field] - ); + const unmapped_field_aliases = interception.response.body.response.unmapped_field_aliases + .map((field) => [field]) + .sort() + .slice(0, 10); Cypress.log({ message: `Validate table data - ${message}`, @@ -162,18 +163,6 @@ const createDetector = (detectorName, dataSource, expectFailure) => { cy.validateDetailsItem('Last updated time', '-'); cy.validateDetailsItem('Detector dashboard', 'Not available for this log type'); - if (!expectFailure) { - let fields = []; - for (let field in dns_mapping_fields) { - fields.push([field, dns_mapping_fields[field]]); - } - cy.getElementByText('.euiTitle', 'Field mapping') - .parentsUntil('.euiPanel') - .siblings() - .eq(2) - .validateTable(fields); - } - validateAlertPanel('test_trigger'); cy.intercept('POST', '/mappings').as('createMappingsRequest'); @@ -206,22 +195,6 @@ const createDetector = (detectorName, dataSource, expectFailure) => { cy.wait(5000); // waiting for the page to be reloaded after pushing detector id into route cy.getElementByText('button.euiTab', 'Alert triggers').should('be.visible').click(); validateAlertPanel('test_trigger'); - - cy.intercept('GET', '/mappings?indexName').as('getMappingFields'); - cy.getElementByText('button.euiTab', 'Field mappings').should('be.visible').click(); - if (!expectFailure) { - let fields = []; - for (let field in dns_mapping_fields) { - fields.push([field, dns_mapping_fields[field]]); - } - cy.wait('@getMappingFields'); - cy.wait(2000); - cy.getElementByText('.euiTitle', 'Field mapping') - .parentsUntil('.euiPanel') - .siblings() - .eq(2) - .validateTable(fields); - } }); }); });