Skip to content

Commit 7001449

Browse files
opensearch-trigger-bot[bot]amsiglan
authored andcommitted
Show surrounding documents when index pattern is available; Finding flyout UI polish (opensearch-project#216) (opensearch-project#245)
* refactored finding flyout Signed-off-by: Amardeepsingh Siglani <[email protected]> * updated cypress test Signed-off-by: Amardeepsingh Siglani <[email protected]> * show surrounding documents when index-pattern available Signed-off-by: Amardeepsingh Siglani <[email protected]> * fixed search filter for log source Signed-off-by: Amardeepsingh Siglani <[email protected]> Signed-off-by: Amardeepsingh Siglani <[email protected]> (cherry picked from commit 98c2b5c) Co-authored-by: Amardeepsingh Siglani <[email protected]> Signed-off-by: AWSHurneyt <[email protected]>
1 parent 542e317 commit 7001449

File tree

9 files changed

+174
-109
lines changed

9 files changed

+174
-109
lines changed

cypress/integration/3_alerts.spec.js

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -249,51 +249,45 @@ describe('Alerts', () => {
249249
['low', 'windows', 'attack.initial_access', 'attack.t1200'].forEach((tag) => {
250250
cy.get('[data-test-subj="finding-details-flyout-rule-tags"]').contains(tag);
251251
});
252-
253-
// Confirm the rule document ID is present
254-
cy.get('[data-test-subj="finding-details-flyout-rule-document-id"]')
255-
.invoke('text')
256-
.then((text) => expect(text).to.not.equal('-'));
257-
258-
// Confirm the rule index
259-
cy.get('[data-test-subj="finding-details-flyout-rule-document-index"]').contains(testIndex);
260-
261-
// Confirm the rule document matches
262-
// The EuiCodeEditor used for this component stores each line of the JSON in an array of elements;
263-
// so this test formats the expected document into an array of strings,
264-
// and matches each entry with the corresponding element line.
265-
const document = JSON.stringify(
266-
[
267-
{
268-
index: 'sample_alerts_spec_cypress_test_index',
269-
id: '',
270-
found: true,
271-
document:
272-
'{"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"}',
273-
},
274-
],
275-
null,
276-
4
277-
);
278-
const documentLines = document.split('\n');
279-
cy.get('[data-test-subj="finding-details-flyout-rule-document"]')
280-
.get('[class="euiCodeBlock__line"]')
281-
.each((lineElement, lineIndex) => {
282-
let line = lineElement.text();
283-
let expectedLine = documentLines[lineIndex];
284-
285-
// The document ID field is generated when the document is added to the index,
286-
// so this test just checks that the line starts with the ID key.
287-
if (expectedLine.trimStart().startsWith('"id": "')) {
288-
expectedLine = '"id": "';
289-
expect(line, `document JSON line ${lineIndex}`).to.contain(expectedLine);
290-
} else {
291-
line = line.replaceAll('\n', '');
292-
expect(line, `document JSON line ${lineIndex}`).to.equal(expectedLine);
293-
}
294-
});
295252
});
296253

254+
// Confirm the rule document ID is present
255+
cy.get('[data-test-subj="finding-details-flyout-rule-document-id"]')
256+
.invoke('text')
257+
.then((text) => expect(text).to.not.equal('-'));
258+
259+
// Confirm the rule index
260+
cy.get('[data-test-subj="finding-details-flyout-rule-document-index"]').contains(testIndex);
261+
262+
// Confirm the rule document matches
263+
// The EuiCodeEditor used for this component stores each line of the JSON in an array of elements;
264+
// so this test formats the expected document into an array of strings,
265+
// and matches each entry with the corresponding element line.
266+
const document = JSON.stringify(
267+
JSON.parse(
268+
'{"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"}'
269+
),
270+
null,
271+
2
272+
);
273+
const documentLines = document.split('\n');
274+
cy.get('[data-test-subj="finding-details-flyout-rule-document"]')
275+
.get('[class="euiCodeBlock__line"]')
276+
.each((lineElement, lineIndex) => {
277+
let line = lineElement.text();
278+
let expectedLine = documentLines[lineIndex];
279+
280+
// The document ID field is generated when the document is added to the index,
281+
// so this test just checks that the line starts with the ID key.
282+
if (expectedLine.trimStart().startsWith('"id": "')) {
283+
expectedLine = '"id": "';
284+
expect(line, `document JSON line ${lineIndex}`).to.contain(expectedLine);
285+
} else {
286+
line = line.replaceAll('\n', '');
287+
expect(line, `document JSON line ${lineIndex}`).to.equal(expectedLine);
288+
}
289+
});
290+
297291
// Press the "back" button
298292
cy.get(
299293
'[data-test-subj="finding-details-flyout-back-button"]',

public/pages/Alerts/components/AlertFlyout/AlertFlyout.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ import {
2727
errorNotificationToast,
2828
renderTime,
2929
} from '../../../../utils/helpers';
30-
import { FindingsService, RuleService } from '../../../../services';
30+
import { FindingsService, RuleService, OpenSearchService } from '../../../../services';
3131
import FindingDetailsFlyout from '../../../Findings/components/FindingDetailsFlyout';
32-
import { Detector, Rule } from '../../../../../models/interfaces';
32+
import { Detector } from '../../../../../models/interfaces';
3333
import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers';
3434
import { Finding } from '../../../Findings/models/interfaces';
3535
import { NotificationsStart } from 'opensearch-dashboards/public';
@@ -39,17 +39,18 @@ export interface AlertFlyoutProps {
3939
detector: Detector;
4040
findingsService: FindingsService;
4141
ruleService: RuleService;
42+
notifications: NotificationsStart;
43+
opensearchService: OpenSearchService;
4244
onClose: () => void;
4345
onAcknowledge: (selectedItems: AlertItem[]) => void;
44-
notifications: NotificationsStart;
4546
}
4647

4748
export interface AlertFlyoutState {
4849
acknowledged: boolean;
4950
findingFlyoutData?: Finding;
5051
findingItems: Finding[];
5152
loading: boolean;
52-
rules: { [key: string]: Rule };
53+
rules: { [key: string]: RuleSource };
5354
}
5455

5556
export class AlertFlyout extends React.Component<AlertFlyoutProps, AlertFlyoutState> {
@@ -89,7 +90,7 @@ export class AlertFlyout extends React.Component<AlertFlyoutProps, AlertFlyoutSt
8990
} else {
9091
errorNotificationToast(notifications, 'retrieve', 'findings', findingRes.error);
9192
}
92-
} catch (e) {
93+
} catch (e: any) {
9394
errorNotificationToast(notifications, 'retrieve', 'findings', e);
9495
}
9596
await this.getRules();
@@ -143,7 +144,7 @@ export class AlertFlyout extends React.Component<AlertFlyoutProps, AlertFlyoutSt
143144
}
144145
this.setState({ rules: allRules });
145146
}
146-
} catch (e) {
147+
} catch (e: any) {
147148
errorNotificationToast(notifications, 'retrieve', 'rules', e);
148149
}
149150
};

public/pages/Alerts/containers/Alerts/Alerts.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import AlertsService from '../../../../services/AlertsService';
4141
import DetectorService from '../../../../services/DetectorService';
4242
import { AlertItem } from '../../../../../server/models/interfaces';
4343
import { AlertFlyout } from '../../components/AlertFlyout/AlertFlyout';
44-
import { FindingsService, RuleService } from '../../../../services';
44+
import { FindingsService, RuleService, OpenSearchService } from '../../../../services';
4545
import { Detector } from '../../../../../models/interfaces';
4646
import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers';
4747
import { DISABLE_ACKNOWLEDGED_ALERT_HELP_TEXT } from '../../utils/constants';
@@ -61,6 +61,7 @@ export interface AlertsProps {
6161
detectorService: DetectorService;
6262
findingService: FindingsService;
6363
ruleService: RuleService;
64+
opensearchService: OpenSearchService;
6465
notifications: NotificationsStart;
6566
match: match;
6667
}

public/pages/CreateDetector/components/DefineDetector/components/DetectorDataSource/DetectorDataSource.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ export default class DetectorDataSource extends Component<
102102
>
103103
<EuiComboBox
104104
placeholder={'Select an input source for the detector.'}
105-
async={true}
106105
isLoading={loading}
107106
options={indexOptions}
108107
selectedOptions={this.parseOptions(detectorIndices)}

public/pages/Findings/components/FindingDetailsFlyout.tsx

Lines changed: 102 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
EuiAccordion,
99
EuiBadge,
1010
EuiBadgeGroup,
11+
EuiButton,
1112
EuiButtonIcon,
1213
EuiCodeBlock,
1314
EuiFlexGroup,
@@ -26,20 +27,23 @@ import { capitalizeFirstLetter, renderTime } from '../../../utils/helpers';
2627
import { DEFAULT_EMPTY_DATA, ROUTES } from '../../../utils/constants';
2728
import { Finding, Query } from '../models/interfaces';
2829
import { RuleViewerFlyout } from '../../Rules/components/RuleViewerFlyout/RuleViewerFlyout';
29-
import { RuleTableItem } from '../../Rules/utils/helpers';
3030
import { RuleSource } from '../../../../server/models/interfaces';
3131
import { RuleItemInfoBase } from '../../Rules/models/types';
32+
import { OpenSearchService } from '../../../services';
33+
import { RuleTableItem } from '../../Rules/utils/helpers';
3234

3335
interface FindingDetailsFlyoutProps {
3436
finding: Finding;
35-
closeFlyout: () => void;
3637
backButton?: React.ReactNode;
3738
allRules: { [id: string]: RuleSource };
39+
opensearchService: OpenSearchService;
40+
closeFlyout: () => void;
3841
}
3942

4043
interface FindingDetailsFlyoutState {
4144
loading: boolean;
4245
ruleViewerFlyoutData: RuleTableItem | null;
46+
indexPatternId?: string;
4347
}
4448

4549
export default class FindingDetailsFlyout extends Component<
@@ -54,6 +58,14 @@ export default class FindingDetailsFlyout extends Component<
5458
};
5559
}
5660

61+
componentDidMount(): void {
62+
this.getIndexPatternId().then((patternId) => {
63+
if (patternId) {
64+
this.setState({ indexPatternId: patternId });
65+
}
66+
});
67+
}
68+
5769
renderTags = () => {
5870
const { finding } = this.props;
5971
const tags = finding.queries[0].tags || [];
@@ -68,7 +80,7 @@ export default class FindingDetailsFlyout extends Component<
6880
);
6981
};
7082

71-
showRuleDetails = (fullRule, ruleId: string) => {
83+
showRuleDetails = (fullRule: any, ruleId: string) => {
7284
this.setState({
7385
...this.state,
7486
ruleViewerFlyoutData: {
@@ -90,13 +102,7 @@ export default class FindingDetailsFlyout extends Component<
90102
};
91103

92104
renderRuleDetails = (rules: Query[] = []) => {
93-
const {
94-
allRules,
95-
finding: { index, related_doc_ids, document_list },
96-
} = this.props;
97-
const documents = document_list;
98-
const docId = related_doc_ids[0];
99-
const document = documents.filter((doc) => doc.id === docId);
105+
const { allRules } = this.props;
100106
return rules.map((rule, key) => {
101107
const fullRule = allRules[rule.id];
102108
const severity = capitalizeFirstLetter(fullRule.level);
@@ -166,51 +172,98 @@ export default class FindingDetailsFlyout extends Component<
166172
</EuiFormRow>
167173

168174
<EuiSpacer size={'l'} />
175+
</EuiAccordion>
176+
{rules.length > 1 && <EuiHorizontalRule />}
177+
</div>
178+
);
179+
});
180+
};
181+
182+
getIndexPatternId = async () => {
183+
const indexPatterns = await this.props.opensearchService.getIndexPatterns();
184+
const {
185+
finding: { index },
186+
} = this.props;
187+
let patternId;
188+
indexPatterns.map((pattern) => {
189+
const patternName = pattern.attributes.title.replaceAll('*', '.*');
190+
const patternRegex = new RegExp(patternName);
191+
if (index.match(patternRegex)) {
192+
patternId = pattern.id;
193+
}
194+
});
195+
196+
return patternId;
197+
};
169198

199+
renderFindingDocuments() {
200+
const {
201+
finding: { index, document_list, related_doc_ids },
202+
} = this.props;
203+
const documents = document_list;
204+
const docId = related_doc_ids[0];
205+
const matchedDocuments = documents.filter((doc) => doc.id === docId);
206+
const document = matchedDocuments.length > 0 ? matchedDocuments[0].document : '';
207+
const { indexPatternId } = this.state;
208+
209+
return (
210+
<>
211+
<EuiFlexGroup justifyContent="spaceBetween">
212+
<EuiFlexItem>
170213
<EuiTitle size={'s'}>
171214
<h3>Documents</h3>
172215
</EuiTitle>
173-
<EuiSpacer size={'s'} />
174-
175-
<EuiFlexGroup>
176-
<EuiFlexItem>
177-
<EuiFormRow
178-
label={'Document ID'}
179-
data-test-subj={'finding-details-flyout-rule-document-id'}
180-
>
181-
<EuiText>{docId || DEFAULT_EMPTY_DATA}</EuiText>
182-
</EuiFormRow>
183-
</EuiFlexItem>
216+
</EuiFlexItem>
217+
<EuiFlexItem grow={false}>
218+
<EuiButton
219+
href={
220+
indexPatternId
221+
? `discover#/context/${indexPatternId}/${related_doc_ids[0]}`
222+
: `#${ROUTES.FINDINGS}`
223+
}
224+
target={indexPatternId ? '_blank' : undefined}
225+
>
226+
View surrounding documents
227+
</EuiButton>
228+
</EuiFlexItem>
229+
</EuiFlexGroup>
184230

185-
<EuiFlexItem>
186-
<EuiFormRow
187-
label={'Index'}
188-
data-test-subj={'finding-details-flyout-rule-document-index'}
189-
>
190-
<EuiText>{index || DEFAULT_EMPTY_DATA}</EuiText>
191-
</EuiFormRow>
192-
</EuiFlexItem>
193-
</EuiFlexGroup>
231+
<EuiSpacer size={'s'} />
194232

195-
<EuiSpacer size={'m'} />
233+
<EuiFlexGroup>
234+
<EuiFlexItem>
235+
<EuiFormRow
236+
label={'Document ID'}
237+
data-test-subj={'finding-details-flyout-rule-document-id'}
238+
>
239+
<EuiText>{docId || DEFAULT_EMPTY_DATA}</EuiText>
240+
</EuiFormRow>
241+
</EuiFlexItem>
196242

197-
<EuiFormRow fullWidth={true}>
198-
<EuiCodeBlock
199-
language={'json'}
200-
inline={false}
201-
isCopyable={true}
202-
readOnly={true}
203-
data-test-subj={'finding-details-flyout-rule-document'}
204-
>
205-
{JSON.stringify(document, null, 4)}
206-
</EuiCodeBlock>
243+
<EuiFlexItem>
244+
<EuiFormRow
245+
label={'Index'}
246+
data-test-subj={'finding-details-flyout-rule-document-index'}
247+
>
248+
<EuiText>{index || DEFAULT_EMPTY_DATA}</EuiText>
207249
</EuiFormRow>
208-
</EuiAccordion>
209-
{rules.length > 1 && <EuiHorizontalRule />}
210-
</div>
211-
);
212-
});
213-
};
250+
</EuiFlexItem>
251+
</EuiFlexGroup>
252+
253+
<EuiSpacer size={'m'} />
254+
255+
<EuiFormRow fullWidth={true}>
256+
<EuiCodeBlock
257+
language="json"
258+
isCopyable
259+
data-test-subj={'finding-details-flyout-rule-document'}
260+
>
261+
{JSON.stringify(JSON.parse(document), null, 2)}
262+
</EuiCodeBlock>
263+
</EuiFormRow>
264+
</>
265+
);
266+
}
214267

215268
render() {
216269
const {
@@ -304,6 +357,8 @@ export default class FindingDetailsFlyout extends Component<
304357
</EuiTitle>
305358
<EuiSpacer size={'m'} />
306359
{this.renderRuleDetails(queries)}
360+
<EuiSpacer size="l" />
361+
{this.renderFindingDocuments()}
307362
</EuiFlyoutBody>
308363
</EuiFlyout>
309364
);

0 commit comments

Comments
 (0)