From 28edd8effabc91fd6a557aa461253680755c1312 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Wed, 3 May 2023 23:00:14 +0200 Subject: [PATCH] [BUG] Finding's fly-out has no correlations if open from alerts (#558) * [BUG] Finding's fly-out has no correlations if open from alerts #557 Signed-off-by: Jovan Cvetkovic * code review from https://github.com/opensearch-project/security-analytics-dashboards-plugin/pull/558#discussion_r1178479490 Signed-off-by: Jovan Cvetkovic * cypress tests wait interval updated to 400 Signed-off-by: Jovan Cvetkovic * cypress tests wait interval updated to 400 Signed-off-by: Jovan Cvetkovic --------- Signed-off-by: Jovan Cvetkovic --- .github/workflows/cypress-workflow.yml | 2 +- cypress/integration/1_detectors.spec.js | 2 +- .../components/AlertFlyout/AlertFlyout.tsx | 1 + .../containers/CorrelationRules.tsx | 6 +- .../containers/CorrelationsContainer.tsx | 11 +- .../containers/CreateCorrelationRule.tsx | 2 +- .../DetectorBasicDetailsView.tsx | 2 +- .../DetectorDetails.test.tsx.snap | 4 +- .../DetectorDetailsView.test.tsx.snap | 4 +- .../components/FindingDetailsFlyout.tsx | 28 +++-- .../FindingsTable/FindingsTable.tsx | 1 + public/store/DataStore.ts | 12 +- public/store/FindingsStore.ts | 108 ++++++++++++++++++ 13 files changed, 156 insertions(+), 27 deletions(-) create mode 100644 public/store/FindingsStore.ts diff --git a/.github/workflows/cypress-workflow.yml b/.github/workflows/cypress-workflow.yml index 42ed1aa8b..a28509642 100644 --- a/.github/workflows/cypress-workflow.yml +++ b/.github/workflows/cypress-workflow.yml @@ -105,7 +105,7 @@ jobs: - name: Sleep until OSD server starts - non-windows if: ${{ matrix.os != 'windows-latest' }} - run: sleep 300 + run: sleep 400 shell: bash - name: Install Cypress diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 81a33f2cc..17987b68b 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -113,7 +113,7 @@ const createDetector = (detectorName, dataSource, expectFailure) => { } } - // Confirm entries user has made + // Confirm entries user made cy.contains('Detector details'); cy.contains(detectorName); cy.contains('dns'); diff --git a/public/pages/Alerts/components/AlertFlyout/AlertFlyout.tsx b/public/pages/Alerts/components/AlertFlyout/AlertFlyout.tsx index 5fcc1a878..9c3870339 100644 --- a/public/pages/Alerts/components/AlertFlyout/AlertFlyout.tsx +++ b/public/pages/Alerts/components/AlertFlyout/AlertFlyout.tsx @@ -180,6 +180,7 @@ export class AlertFlyout extends React.Component = (props: RouteComp const getCorrelationRules = useCallback( async (ruleItem?) => { - const allCorrelationRules: CorrelationRuleHit[] = await DataStore.correlationsStore.getCorrelationRules(); + const allCorrelationRules: CorrelationRuleHit[] = await DataStore.correlations.getCorrelationRules(); const allRuleItems: CorrelationRuleTableItem[] = allCorrelationRules.map( (rule: CorrelationRuleHit) => ({ ...rule, @@ -58,7 +58,7 @@ export const CorrelationRules: React.FC = (props: RouteComp setAllRules(allRuleItems); setFilteredRules(allRuleItems); }, - [DataStore.correlationsStore.getCorrelationRules] + [DataStore.correlations.getCorrelationRules] ); useEffect(() => { @@ -113,7 +113,7 @@ export const CorrelationRules: React.FC = (props: RouteComp const onDeleteRuleConfirmed = async (rule: any) => { if (selectedRule) { - const response = await DataStore.correlationsStore.deleteCorrelationRule(selectedRule.id); + const response = await DataStore.correlations.deleteCorrelationRule(selectedRule.id); if (response) { closeDeleteModal(); diff --git a/public/pages/Correlations/containers/CorrelationsContainer.tsx b/public/pages/Correlations/containers/CorrelationsContainer.tsx index b3bc8f010..081973f42 100644 --- a/public/pages/Correlations/containers/CorrelationsContainer.tsx +++ b/public/pages/Correlations/containers/CorrelationsContainer.tsx @@ -153,7 +153,7 @@ export class Correlations extends React.Component { // get finding data and set the specificFindingInfo - const specificFindingInfo = await DataStore.correlationsStore.getCorrelatedFindings( - id, - logType - ); + const specificFindingInfo = await DataStore.correlations.getCorrelatedFindings(id, logType); this.setState({ specificFindingInfo }); this.updateGraphDataState(specificFindingInfo); }; diff --git a/public/pages/Correlations/containers/CreateCorrelationRule.tsx b/public/pages/Correlations/containers/CreateCorrelationRule.tsx index 58c3320ce..6d3b1efed 100644 --- a/public/pages/Correlations/containers/CreateCorrelationRule.tsx +++ b/public/pages/Correlations/containers/CreateCorrelationRule.tsx @@ -52,7 +52,7 @@ export interface CorrelationOptions { export const CreateCorrelationRule: React.FC = ( props: CreateCorrelationRuleProps ) => { - const correlationStore = DataStore.correlationsStore; + const correlationStore = DataStore.correlations; const validateCorrelationRule = useCallback((rule: CorrelationRuleModel) => { if (!rule.name) { return 'Invalid rule name'; diff --git a/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx b/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx index 6b38432a1..79a3eb6c5 100644 --- a/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx +++ b/public/pages/Detectors/components/DetectorBasicDetailsView/DetectorBasicDetailsView.tsx @@ -72,7 +72,7 @@ export const DetectorBasicDetailsView: React.FC = content: ( <> {inputs[0].detector_input.indices.map((ind: string) => ( - {ind} + {ind} ))} ), 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 75c01fb24..d0da566e6 100644 --- a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap +++ b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap @@ -1854,7 +1854,9 @@ exports[` spec renders the component 1`] = ` onBlur={[Function]} onFocus={[Function]} > - +
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 c31dc65f6..dbeb80f0b 100644 --- a/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap +++ b/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap @@ -897,7 +897,9 @@ exports[` spec renders the component 1`] = ` onBlur={[Function]} onFocus={[Function]} > - +
diff --git a/public/pages/Findings/components/FindingDetailsFlyout.tsx b/public/pages/Findings/components/FindingDetailsFlyout.tsx index 948899f60..1b4fd6035 100644 --- a/public/pages/Findings/components/FindingDetailsFlyout.tsx +++ b/public/pages/Findings/components/FindingDetailsFlyout.tsx @@ -57,6 +57,7 @@ interface FindingDetailsFlyoutProps extends RouteComponentProps { indexPatternsService: IndexPatternsService; correlationService: CorrelationService; closeFlyout: () => void; + shouldLoadAllFindings: boolean; } interface FindingDetailsFlyoutState { @@ -83,16 +84,15 @@ export default class FindingDetailsFlyout extends Component< }; } - componentDidMount(): void { - this.getIndexPatternId().then((patternId) => { - if (patternId) { - this.setState({ indexPatternId: patternId }); - } - }); - + getCorrelations = async () => { const { id, detector } = this.props.finding; - const allFindings = this.props.findings; - DataStore.correlationsStore + let allFindings = this.props.findings; + if (this.props.shouldLoadAllFindings) { + // if findings come from the alerts fly-out, we need to get all the findings to match those with the correlations + allFindings = await DataStore.findings.getAllFindings(); + } + + DataStore.correlations .getCorrelatedFindings(id, detector._source?.detector_type) .then((findings) => { if (findings?.correlatedFindings.length) { @@ -107,6 +107,16 @@ export default class FindingDetailsFlyout extends Component< this.setState({ correlatedFindings }); } }); + }; + + componentDidMount(): void { + this.getIndexPatternId().then((patternId) => { + if (patternId) { + this.setState({ indexPatternId: patternId }); + } + }); + + this.getCorrelations(); this.setState({ selectedTab: { diff --git a/public/pages/Findings/components/FindingsTable/FindingsTable.tsx b/public/pages/Findings/components/FindingsTable/FindingsTable.tsx index 2b0c2fa3b..3009fbd18 100644 --- a/public/pages/Findings/components/FindingsTable/FindingsTable.tsx +++ b/public/pages/Findings/components/FindingsTable/FindingsTable.tsx @@ -134,6 +134,7 @@ export default class FindingsTable extends Component { const rulesStore = new RulesStore(services.ruleService, notifications); @@ -25,7 +27,13 @@ export class DataStore { services.savedObjectsService ); - DataStore.correlationsStore = new CorrelationsStore( + DataStore.findings = new FindingsStore( + services.findingsService, + services.detectorsService, + notifications + ); + + DataStore.correlations = new CorrelationsStore( services.correlationsService, services.detectorsService, services.findingsService, diff --git a/public/store/FindingsStore.ts b/public/store/FindingsStore.ts new file mode 100644 index 000000000..4a65bc6a5 --- /dev/null +++ b/public/store/FindingsStore.ts @@ -0,0 +1,108 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DetectorsService, FindingsService } from '../services'; +import { NotificationsStart } from 'opensearch-dashboards/public'; +import { RouteComponentProps } from 'react-router-dom'; +import { errorNotificationToast } from '../utils/helpers'; +import { FindingItemType } from '../pages/Findings/containers/Findings/Findings'; + +export interface IFindingsStore { + readonly service: FindingsService; + + readonly detectorsService: DetectorsService; + + readonly notifications: NotificationsStart; + + getFindingsPerDetector: (detectorId: string) => Promise; + + getAllFindings: () => Promise; +} + +export interface IFindingsCache {} + +/** + * Findings store + * + * @class FindingsStore + * @implements IDetectorsStore + * @param {BrowserServices} services Uses services to make API requests + */ +export class FindingsStore implements IFindingsStore { + /** + * Findings service instance + * + * @property {FindingsService} service + * @readonly + */ + readonly service: FindingsService; + + /** + * Detectors service instance + * + * @property {DetectorsService} detectorsService + * @readonly + */ + readonly detectorsService: DetectorsService; + + /** + * Notifications + * @property {NotificationsStart} + * @readonly + */ + readonly notifications: NotificationsStart; + + /** + * Router history + * @property {RouteComponentProps['history']} + * @readonly + */ + history: RouteComponentProps['history'] | undefined = undefined; + + constructor( + service: FindingsService, + detectorsService: DetectorsService, + notifications: NotificationsStart + ) { + this.service = service; + this.detectorsService = detectorsService; + this.notifications = notifications; + } + + public getFindingsPerDetector = async (detectorId: string): Promise => { + let allFindings: FindingItemType[] = []; + const findingRes = await this.service.getFindings({ detectorId }); + if (findingRes.ok) { + allFindings = findingRes.response.findings as FindingItemType[]; + } else { + errorNotificationToast(this.notifications, 'retrieve', 'findings', findingRes.error); + } + + return allFindings; + }; + + public getAllFindings = async (): Promise => { + let allFindings: FindingItemType[] = []; + const detectorsRes = await this.detectorsService.getDetectors(); + if (detectorsRes.ok) { + const detectors = detectorsRes.response.hits.hits; + + for (let detector of detectors) { + const findings = await this.getFindingsPerDetector(detector._id); + const findingsPerDetector: FindingItemType[] = findings.map((finding) => { + return { + ...finding, + detectorName: detector._source.name, + logType: detector._source.detector_type, + detector: detector, + }; + }); + allFindings = allFindings.concat(findingsPerDetector); + } + } + + return allFindings; + }; +}