From 88bed3e3d8a08f976f23be055f7eb586df78b720 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 18 Apr 2023 19:23:18 +0200 Subject: [PATCH] Feature] deleting detectors should delete all related dashboards (including index patterns and visualisations) (#515) * Update detector details component #504 Signed-off-by: Jovan Cvetkovic * [FEATURE] Deleting detectors should delete all related dashboards (including index-patterns and visualisations) #509 Signed-off-by: Jovan Cvetkovic * [FEATURE] Deleting detectors should delete all related dashboards (including index-patterns and visualisations) #509 Signed-off-by: Jovan Cvetkovic * [FEATURE] Deleting detectors should delete all related dashboards (including index-patterns and visualisations) #509 Signed-off-by: Jovan Cvetkovic * [FEATURE] Deleting detectors should delete all related dashboards (including index-patterns and visualisations) #509 Signed-off-by: Jovan Cvetkovic * [FEATURE] Deleting detectors should delete all related dashboards (including index-patterns and visualisations) #509 Signed-off-by: Jovan Cvetkovic * [FEATURE] Deleting detectors should delete all related dashboards (including index-patterns and visualisations) #509 Signed-off-by: Jovan Cvetkovic * [FEATURE] Deleting detectors should delete all related dashboards (including index-patterns and visualisations) #509 Signed-off-by: Jovan Cvetkovic --------- Signed-off-by: Jovan Cvetkovic --- .../rule/create_network_rule.json | 26 +++++++++++++ .../sample_network_index_settings.json | 39 +++++++++++++++++++ .../AlertTriggerView/AlertTriggerView.tsx | 22 ++++------- .../DetectorBasicDetailsView.tsx | 6 ++- .../containers/Detector/DetectorDetails.tsx | 26 ++++++++++++- .../DetectorDetails.test.tsx.snap | 19 +++++++++ .../DetectorDetailsView.tsx | 18 +++++---- .../DetectorDetailsView.test.tsx.snap | 7 ++++ public/pages/Main/Main.tsx | 1 + public/services/IndexPatternsService.ts | 31 +++++++++++++++ public/services/SavedObjectService.ts | 33 +++++++++++++++- public/store/DetectorsStore.tsx | 20 +++++----- types/services/ISavedObjectsService.ts | 5 +++ 13 files changed, 219 insertions(+), 34 deletions(-) create mode 100644 cypress/fixtures/integration_tests/rule/create_network_rule.json create mode 100644 cypress/fixtures/sample_network_index_settings.json diff --git a/cypress/fixtures/integration_tests/rule/create_network_rule.json b/cypress/fixtures/integration_tests/rule/create_network_rule.json new file mode 100644 index 000000000..43e69cff4 --- /dev/null +++ b/cypress/fixtures/integration_tests/rule/create_network_rule.json @@ -0,0 +1,26 @@ +{ + "id": "25b9c01c-350d-4b95-bed1-836d04a4f326", + "category": "network", + "title": "Cypress Network Rule", + "description": "Detects network changes", + "status": "experimental", + "author": "Cypress Tests", + "references": [ + { + "value": "" + } + ], + "tags": [ + { + "value": "network.low" + } + ], + "log_source": "", + "detection": "selection:\n keywords:\n - erase\n - delete\n - YXC\ncondition: selection", + "level": "low", + "false_positives": [ + { + "value": "" + } + ] +} diff --git a/cypress/fixtures/sample_network_index_settings.json b/cypress/fixtures/sample_network_index_settings.json new file mode 100644 index 000000000..c1451abf3 --- /dev/null +++ b/cypress/fixtures/sample_network_index_settings.json @@ -0,0 +1,39 @@ +{ + "mappings": { + "properties": { + "name": { + "type": "text" + }, + "path": { + "type": "text" + }, + "keywords": { + "type": "text" + }, + "operation": { + "type": "text" + }, + "action": { + "type": "text" + }, + "dst_port": { + "type": "integer" + }, + "timeframe": { + "type": "text" + }, + "user_agent": { + "type": "text" + }, + "method": { + "type": "text" + } + } + }, + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "1" + } + } +} 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 }, + ])} diff --git a/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx b/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx index 146ffd4a6..6b38432a1 100644 --- a/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx +++ b/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ContentPanel } from '../../../../components/ContentPanel'; import { createTextDetailsGroup, parseSchedule } from '../../../../utils/helpers'; import moment from 'moment'; -import { DEFAULT_EMPTY_DATA } from '../../../../utils/constants'; +import { DEFAULT_EMPTY_DATA, logTypesWithDashboards } from '../../../../utils/constants'; import { Detector } from '../../../../../types'; export interface DetectorBasicDetailsViewProps { @@ -85,8 +85,10 @@ export const DetectorBasicDetailsView: React.FC = {`${name} summary`} - ) : ( + ) : !logTypesWithDashboards.has(detector_type) ? ( 'Not available for this log type' + ) : ( + '-' )) as any, }, ])} diff --git a/public/pages/Detectors/containers/Detector/DetectorDetails.tsx b/public/pages/Detectors/containers/Detector/DetectorDetails.tsx index c423817b6..e8f908645 100644 --- a/public/pages/Detectors/containers/Detector/DetectorDetails.tsx +++ b/public/pages/Detectors/containers/Detector/DetectorDetails.tsx @@ -26,7 +26,7 @@ import { DetectorDetailsView } from '../DetectorDetailsView/DetectorDetailsView' import { FieldMappingsView } from '../../components/FieldMappingsView/FieldMappingsView'; import { AlertTriggersView } from '../AlertTriggersView/AlertTriggersView'; import { RuleItem } from '../../../CreateDetector/components/DefineDetector/components/DetectionRules/types/interfaces'; -import { DetectorsService } from '../../../../services'; +import { DetectorsService, IndexPatternsService } from '../../../../services'; import { errorNotificationToast } from '../../../../utils/helpers'; import { NotificationsStart, SimpleSavedObject } from 'opensearch-dashboards/public'; import { ISavedObjectsService, ServerResponse } from '../../../../../types'; @@ -47,6 +47,7 @@ export interface DetectorDetailsProps detectorService: DetectorsService; notifications: NotificationsStart; savedObjectsService: ISavedObjectsService; + indexPatternsService: IndexPatternsService; } export interface DetectorDetailsState { @@ -257,6 +258,11 @@ export class DetectorDetails extends React.Component { + if (ref.type === 'visualization') { + await this.props.savedObjectsService.deleteVisualization(ref.id); + } + }); + await this.props.savedObjectsService.deleteDashboard(dashboard.id); + } + + const index = await this.props.indexPatternsService.getIndexPattern(detectorId); + if (index) { + await this.props.indexPatternsService.deleteIndexPattern(index.id); + } + const deleteRes = await detectorService.deleteDetector(detectorId); if (!deleteRes.ok) { errorNotificationToast(notifications, 'delete', 'detector', deleteRes.error); } else { + DataStore.detectors.deleteState(); + DataStore.detectors.clearNotifications(); this.props.history.push(ROUTES.DETECTORS); } } catch (e: any) { 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 544669fb8..9da8535cf 100644 --- a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap +++ b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap @@ -216,6 +216,9 @@ exports[` spec renders the component 1`] = ` } savedObjectsService={ SavedObjectService { + "deleteDashboard": [Function], + "deleteVisualization": [Function], + "getDashboard": [Function], "getDashboards": [Function], "indexService": undefined, "savedObjectsClient": Object { @@ -914,6 +917,9 @@ exports[` spec renders the component 1`] = ` } savedObjectsService={ SavedObjectService { + "deleteDashboard": [Function], + "deleteVisualization": [Function], + "getDashboard": [Function], "getDashboards": [Function], "indexService": undefined, "savedObjectsClient": Object { @@ -1325,6 +1331,9 @@ exports[` spec renders the component 1`] = ` onEditClicked={[Function]} savedObjectsService={ SavedObjectService { + "deleteDashboard": [Function], + "deleteVisualization": [Function], + "getDashboard": [Function], "getDashboards": [Function], "indexService": undefined, "savedObjectsClient": Object { @@ -2308,6 +2317,13 @@ exports[` spec renders the component 1`] = ` + +
+ spec renders the component 1`] = ` onEditClicked={[Function]} savedObjectsService={ SavedObjectService { + "deleteDashboard": [Function], + "deleteVisualization": [Function], + "getDashboard": [Function], "getDashboards": [Function], "indexService": undefined, "savedObjectsClient": Object { diff --git a/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx b/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx index cbafe7f7d..f3e7ea60e 100644 --- a/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx +++ b/public/pages/Detectors/containers/DetectorDetailsView/DetectorDetailsView.tsx @@ -9,6 +9,7 @@ import { DetectorRulesView } from '../../components/DetectorRulesView/DetectorRu import { RuleItem } from '../../../CreateDetector/components/DefineDetector/components/DetectionRules/types/interfaces'; import { NotificationsStart } from 'opensearch-dashboards/public'; import { Detector } from '../../../../../types'; +import { EuiSpacer } from '@elastic/eui'; export interface DetectorDetailsViewProps { detector: Detector; @@ -40,13 +41,16 @@ export class DetectorDetailsView extends React.Component< isEditable = true, } = this.props; const detectorRules = ( - + <> + + + ); return ( 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 3e6ecf6cc..a1f79bdea 100644 --- a/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap +++ b/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap @@ -1360,6 +1360,13 @@ exports[` spec renders the component 1`] = ` + +
+ { diff --git a/public/services/IndexPatternsService.ts b/public/services/IndexPatternsService.ts index 309f8fd16..69796a37e 100644 --- a/public/services/IndexPatternsService.ts +++ b/public/services/IndexPatternsService.ts @@ -8,6 +8,8 @@ import { IndexPatternSpec, IndexPatternsService as CoreIndexPatternsService, } from '../../../../src/plugins/data/common/index_patterns'; +import { IndexPatternSavedObjectAttrs } from '../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { SavedObject } from '../../../../src/plugins/data/common'; export default class IndexPatternsService { constructor(private coreIndexPatternsService: CoreIndexPatternsService) {} @@ -19,4 +21,33 @@ export default class IndexPatternsService { async createAndSave(spec: IndexPatternSpec, override = false, skipFetchFields = false) { return this.coreIndexPatternsService.createAndSave(spec, override, skipFetchFields); } + + public getIndexPatterns = async (): Promise< + SavedObject[] | null | undefined + > => { + const indexPatterns = await this.coreIndexPatternsService.getCache(); + + return Promise.resolve(indexPatterns); + }; + + public getIndexPattern = async ( + detectorId: string + ): Promise> | Promise> => { + let indexPattern; + const indexPatterns = await this.getIndexPatterns(); + console.log('indexPatterns', indexPatterns); + indexPatterns?.some((indexRef) => { + if (indexRef.references.findIndex((reference) => reference.id === detectorId) > -1) { + indexPattern = indexRef; + return true; + } + + return false; + }); + + return indexPattern; + }; + + public deleteIndexPattern = async (indexPatternId: string) => + await this.coreIndexPatternsService.delete(indexPatternId); } diff --git a/public/services/SavedObjectService.ts b/public/services/SavedObjectService.ts index 6dd6d91e9..040a823cd 100644 --- a/public/services/SavedObjectService.ts +++ b/public/services/SavedObjectService.ts @@ -39,10 +39,17 @@ export default class SavedObjectService implements ISavedObjectsService { }, }; }); - const createAliasRes = await this.indexService.updateAliases({ + await this.indexService.updateAliases({ actions: indexActions, }); + const detectorReferences = [ + { + id: detectorId, + name: name, + type: 'detector-SA', + }, + ]; const indexPattern = await this.savedObjectsClient.create( savedObjectConfig['index-pattern'].type, { @@ -51,6 +58,7 @@ export default class SavedObjectService implements ISavedObjectsService { }, { ...savedObjectConfig['index-pattern'], + references: detectorReferences, } ); @@ -130,6 +138,23 @@ export default class SavedObjectService implements ISavedObjectsService { return Promise.resolve(dashboards); }; + public getDashboard = async ( + detectorId: string + ): Promise> | Promise> => { + let dashboard; + const dashboards = await this.getDashboards(); + dashboards?.some((dashRef) => { + if (dashRef.references.findIndex((reference) => reference.id === detectorId) > -1) { + dashboard = dashRef; + return true; + } + + return false; + }); + + return dashboard; + }; + public async getIndexPatterns(): Promise[]> { const indexPatterns = await this.savedObjectsClient .find<{ title: string }>({ @@ -141,4 +166,10 @@ export default class SavedObjectService implements ISavedObjectsService { return Promise.resolve(indexPatterns); } + + public deleteDashboard = async (dashboardId: string) => + await this.savedObjectsClient.delete('dashboard', dashboardId); + + public deleteVisualization = async (visualizationId: string) => + await this.savedObjectsClient.delete('visualization', visualizationId); } diff --git a/public/store/DetectorsStore.tsx b/public/store/DetectorsStore.tsx index 9377d06d7..40c388f7f 100644 --- a/public/store/DetectorsStore.tsx +++ b/public/store/DetectorsStore.tsx @@ -25,6 +25,7 @@ export interface IDetectorsStore { setState: (state: IDetectorsState, history: RouteComponentProps['history']) => void; getState: () => IDetectorsState | undefined; deleteState: () => void; + clearNotifications: () => void; resolvePendingCreationRequest: () => Promise<{ detectorId?: string; dashboardId?: string; @@ -132,6 +133,12 @@ export class DetectorsStore implements IDetectorsStore { delete this.state; }; + public clearNotifications = (): void => { + this.hideCallout(); + this.toasts = []; + this.showToastCallback([]); + }; + private showNotification = ( title: string, message?: string, @@ -258,15 +265,10 @@ export class DetectorsStore implements IDetectorsStore { 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 dashboard = await this.savedObjectsService.getDashboard(detectorId); + if (dashboard) { + dashboardId = dashboard.id; + } } } diff --git a/types/services/ISavedObjectsService.ts b/types/services/ISavedObjectsService.ts index 92902be38..f42c713b4 100644 --- a/types/services/ISavedObjectsService.ts +++ b/types/services/ISavedObjectsService.ts @@ -16,4 +16,9 @@ export interface ISavedObjectsService { getDashboards(): Promise< SimpleSavedObject<{ references: SavedObjectReference[]; id?: string }>[] >; + getDashboard( + detectorId: string + ): Promise> | Promise>; + deleteDashboard(detectorId: string): Promise; + deleteVisualization(detectorId: string): Promise; }