From cdacb14be72f8fafb784505f159845d70a4e27aa Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 06:14:14 +0530 Subject: [PATCH] [Backport 2.13] Show all related docs for a finding (#1011) * Show all related docs for a finding (#1006) * refactored Ux to show all documents for a finding Signed-off-by: Amardeepsingh Siglani * updated snapshots Signed-off-by: Amardeepsingh Siglani * reverting snapshots Signed-off-by: Amardeepsingh Siglani * updated cypress test Signed-off-by: Amardeepsingh Siglani * updated cypress test Signed-off-by: Amardeepsingh Siglani * refactored code Signed-off-by: Amardeepsingh Siglani --------- Signed-off-by: Amardeepsingh Siglani (cherry picked from commit e16e1aaffe5bf207070a5d88e85daace6aca2707) Signed-off-by: github-actions[bot] * updated refs in workflow Signed-off-by: Amardeepsingh Siglani --------- Signed-off-by: Amardeepsingh Siglani Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] Co-authored-by: Amardeepsingh Siglani --- .github/workflows/cypress-workflow.yml | 4 +- .github/workflows/unit-tests-workflow.yml | 2 +- cypress/integration/3_alerts.spec.js | 11 +- .../components/FindingDetailsFlyout.tsx | 219 +++++++++++------- types/Finding.ts | 4 + 5 files changed, 146 insertions(+), 94 deletions(-) diff --git a/.github/workflows/cypress-workflow.yml b/.github/workflows/cypress-workflow.yml index 391e4a224..ec7b7d6a0 100644 --- a/.github/workflows/cypress-workflow.yml +++ b/.github/workflows/cypress-workflow.yml @@ -7,8 +7,8 @@ on: branches: - "*" env: - OPENSEARCH_DASHBOARDS_VERSION: '2.x' - SECURITY_ANALYTICS_BRANCH: '2.x' + OPENSEARCH_DASHBOARDS_VERSION: '2.13.0' + SECURITY_ANALYTICS_BRANCH: '2.13' jobs: tests: name: Run Cypress E2E tests diff --git a/.github/workflows/unit-tests-workflow.yml b/.github/workflows/unit-tests-workflow.yml index 3b3c10cb9..f5878a5d7 100644 --- a/.github/workflows/unit-tests-workflow.yml +++ b/.github/workflows/unit-tests-workflow.yml @@ -7,7 +7,7 @@ on: branches: - "*" env: - OPENSEARCH_DASHBOARDS_VERSION: '2.x' + OPENSEARCH_DASHBOARDS_VERSION: '2.13.0' jobs: Get-CI-Image-Tag: uses: opensearch-project/opensearch-build/.github/workflows/get-ci-image-tag.yml@main diff --git a/cypress/integration/3_alerts.spec.js b/cypress/integration/3_alerts.spec.js index d3fa53007..98c8ba036 100644 --- a/cypress/integration/3_alerts.spec.js +++ b/cypress/integration/3_alerts.spec.js @@ -210,21 +210,20 @@ describe('Alerts', () => { }); }); - // Confirm the rule document ID is present - cy.get('[data-test-subj="finding-details-flyout-rule-document-id"]') - .invoke('text') - .then((text) => expect(text).to.not.equal('-')); - // Confirm the rule index cy.get('[data-test-subj="finding-details-flyout-rule-document-index"]').contains(indexName); + // Confirm there is atleast one row of document + cy.get('tbody > tr').should('have.length.least', 1); + // 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('{"winlog.event_id": 2003}'), null, 2); const documentLines = document.split('\n'); - cy.get('[data-test-subj="finding-details-flyout-rule-document"]') + cy.get('[data-test-subj="finding-details-flyout-document-toggle-0"]').click({ force: true }); + cy.get('[data-test-subj="finding-details-flyout-rule-document-0"]') .get('[class="euiCodeBlock__line"]') .each((lineElement, lineIndex) => { let line = lineElement.text(); diff --git a/public/pages/Findings/components/FindingDetailsFlyout.tsx b/public/pages/Findings/components/FindingDetailsFlyout.tsx index 2871f87c9..468826947 100644 --- a/public/pages/Findings/components/FindingDetailsFlyout.tsx +++ b/public/pages/Findings/components/FindingDetailsFlyout.tsx @@ -8,7 +8,6 @@ import { EuiAccordion, EuiBadge, EuiBadgeGroup, - EuiButton, EuiButtonIcon, EuiCodeBlock, EuiFlexGroup, @@ -27,12 +26,14 @@ import { EuiSpacer, EuiText, EuiTitle, - EuiIcon, EuiTabs, EuiTab, EuiLoadingContent, - EuiEmptyPrompt, EuiLoadingSpinner, + EuiBasicTableColumn, + EuiInMemoryTable, + EuiToolTip, + EuiEmptyPrompt, } from '@elastic/eui'; import { capitalizeFirstLetter, @@ -48,7 +49,7 @@ import { OpenSearchService, IndexPatternsService, CorrelationService } from '../ import { RuleTableItem } from '../../Rules/utils/helpers'; import { CreateIndexPatternForm } from './CreateIndexPatternForm'; import { FindingItemType } from '../containers/Findings/Findings'; -import { CorrelationFinding, RuleItemInfoBase } from '../../../../types'; +import { CorrelationFinding, FindingDocumentItem, RuleItemInfoBase } from '../../../../types'; import { FindingFlyoutTabId, FindingFlyoutTabs } from '../utils/constants'; import { DataStore } from '../../../store/DataStore'; import { CorrelationsTable } from './CorrelationsTable/CorrelationsTable'; @@ -74,11 +75,12 @@ interface FindingDetailsFlyoutState { ruleViewerFlyoutData: RuleTableItem | null; indexPatternId?: string; isCreateIndexPatternModalVisible: boolean; - selectedTab: { id: string; content: React.ReactNode | null }; + selectedTab: { id: FindingFlyoutTabId; content: React.ReactNode | null }; correlatedFindings: CorrelationFinding[]; allRules: { [id: string]: RuleSource }; - isDocumentLoading: boolean; + loadingIndexPatternId: boolean; areCorrelationsLoading: boolean; + docIdToExpandedRowMap: { [id: string]: JSX.Element }; } export default class FindingDetailsFlyout extends Component< @@ -104,9 +106,10 @@ export default class FindingDetailsFlyout extends Component< ), }, correlatedFindings: [], - isDocumentLoading: true, + loadingIndexPatternId: true, areCorrelationsLoading: true, allRules: {}, + docIdToExpandedRowMap: {}, }; } @@ -155,7 +158,7 @@ export default class FindingDetailsFlyout extends Component< } }) .finally(() => { - this.setState({ isDocumentLoading: false }); + this.setState({ loadingIndexPatternId: false }); }); this.getCorrelations(); @@ -174,6 +177,21 @@ export default class FindingDetailsFlyout extends Component< }); } + componentDidUpdate( + prevProps: Readonly, + prevState: Readonly, + snapshot?: any + ): void { + if (prevState.docIdToExpandedRowMap !== this.state.docIdToExpandedRowMap) { + this.setState({ + selectedTab: { + id: this.state.selectedTab.id, + content: this.getTabContent(this.state.selectedTab.id, this.state.loadingIndexPatternId), + }, + }); + } + } + renderTags = (tags: string[]) => { return ( tags && ( @@ -300,97 +318,127 @@ export default class FindingDetailsFlyout extends Component< return patternId; }; - renderFindingDocuments(isDocumentLoading: boolean) { - const { - finding: { index, document_list, related_doc_ids }, - } = this.props; - const documents = document_list; - const docId = related_doc_ids[0]; - const matchedDocuments = documents.filter((doc) => doc.id === docId); - const document = matchedDocuments.length > 0 ? matchedDocuments[0].document : ''; + toggleDocumentDetails(item: FindingDocumentItem) { + const docIdToExpandedRowMapValues = { ...this.state.docIdToExpandedRowMap }; let formattedDocument = ''; try { - formattedDocument = document ? JSON.stringify(JSON.parse(document), null, 2) : ''; + formattedDocument = document ? JSON.stringify(JSON.parse(item.document), null, 2) : ''; } catch { // no-op } - const { indexPatternId } = this.state; + if (docIdToExpandedRowMapValues[item.id]) { + delete docIdToExpandedRowMapValues[item.id]; + } else { + docIdToExpandedRowMapValues[item.id] = ( + + + {formattedDocument} + + + ); + } + + this.setState({ docIdToExpandedRowMap: docIdToExpandedRowMapValues }); + } - return document ? ( - <> - - - -

Documents

-
-
- - { + if (documentInfo.found && relatedDocIdsSet.has(documentInfo.id)) { + relatedDocuments.push({ ...documentInfo, itemIdx: relatedDocuments.length }); + } + }); + + if (relatedDocuments.length === 0) { + return ( + <> + +

Documents

+
+ + Document not found} + body={

The document that generated this finding could not be loaded.

} + /> + + ); + } + + const actions = [ + { + render: ({ id }: FindingDocumentItem) => ( + + { if (indexPatternId) { - window.open( - `discover#/context/${indexPatternId}/${related_doc_ids[0]}`, - '_blank' - ); + window.open(`discover#/context/${indexPatternId}/${id}`, '_blank'); } else { this.setState({ ...this.state, isCreateIndexPatternModalVisible: true }); } }} - > - View surrounding documents - -
-
-
- - - - - - - {docId || DEFAULT_EMPTY_DATA} - - - - - - {index || DEFAULT_EMPTY_DATA} - - - - - + /> + + ), + }, + ]; + + const documentsColumns: EuiBasicTableColumn[] = [ + { + name: '', + render: (item: FindingDocumentItem) => ( + this.toggleDocumentDetails(item)} + aria-label={docIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'} + iconType={docIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} + data-test-subj={`finding-details-flyout-document-toggle-${item.itemIdx}`} + /> + ), + width: '30', + isExpander: true, + }, + { + field: 'id', + name: 'Document Id', + }, + { + name: 'Actions', + actions, + }, + ]; - - - {formattedDocument} - - - - ) : ( + return ( <>

Documents

- Document not found} - body={

The document that generated this finding could not be loaded.

} + + {index || DEFAULT_EMPTY_DATA} + + + ); @@ -400,6 +448,7 @@ export default class FindingDetailsFlyout extends Component< const { finding: { related_doc_ids }, } = this.props; + if (this.state.isCreateIndexPatternModalVisible) { return ( (); @@ -473,11 +522,11 @@ export default class FindingDetailsFlyout extends Component< ); case FindingFlyoutTabId.DETAILS: default: - return this.createFindingDetails(isDocumentLoading); + return this.createFindingDetails(loadingIndexPatternId); } } - private createFindingDetails(isDocumentLoading: boolean) { + private createFindingDetails(loadingIndexPatternId: boolean) { const { finding: { queries, detectionType }, } = this.props; @@ -536,7 +585,7 @@ export default class FindingDetailsFlyout extends Component< )} - {this.renderFindingDocuments(isDocumentLoading)} + {this.renderFindingDocuments(loadingIndexPatternId)} ); } @@ -546,7 +595,7 @@ export default class FindingDetailsFlyout extends Component< const { finding: { id, timestamp, detectionType }, } = this.props; - const { isDocumentLoading } = this.state; + const { loadingIndexPatternId } = this.state; return (