Skip to content

Commit 9866538

Browse files
committed
[SIEM] Add link to endpoint app through reference.url (#56211)
* add rule.reference * Fix Load more * Fix spacing * Fix loading on hist graph detections * add tooltip
1 parent c540890 commit 9866538

17 files changed

Lines changed: 287 additions & 34 deletions

File tree

x-pack/legacy/plugins/siem/public/components/alerts_viewer/default_headers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const alertsHeaders: ColumnHeader[] = [
1919
columnHeaderType: defaultColumnHeaderType,
2020
id: 'event.module',
2121
width: DEFAULT_COLUMN_MIN_WIDTH,
22+
linkField: 'rule.reference',
2223
},
2324
{
2425
columnHeaderType: defaultColumnHeaderType,

x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/constants.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ export const DATE_FIELD_TYPE = 'date';
88
export const HOST_NAME_FIELD_NAME = 'host.name';
99
export const IP_FIELD_TYPE = 'ip';
1010
export const MESSAGE_FIELD_NAME = 'message';
11+
export const EVENT_MODULE_FIELD_NAME = 'event.module';
12+
export const RULE_REFERENCE_FIELD_NAME = 'rule.reference';
1113
export const SIGNAL_RULE_NAME_FIELD_NAME = 'signal.rule.name';

x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiLink } from '@elastic/eui';
7+
import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
88
import { isNumber, isString, isEmpty } from 'lodash/fp';
99
import React from 'react';
1010

@@ -15,16 +15,19 @@ import { getOrEmptyTagFromValue, getEmptyTagValue } from '../../../empty_value';
1515
import { FormattedDate } from '../../../formatted_date';
1616
import { FormattedIp } from '../../../formatted_ip';
1717
import { HostDetailsLink } from '../../../links';
18-
import { getRuleDetailsUrl } from '../../../link_to/redirect_to_detection_engine';
18+
1919
import { Port, PORT_NAMES } from '../../../port';
2020
import { TruncatableText } from '../../../truncatable_text';
2121
import {
2222
DATE_FIELD_TYPE,
2323
HOST_NAME_FIELD_NAME,
2424
IP_FIELD_TYPE,
2525
MESSAGE_FIELD_NAME,
26+
EVENT_MODULE_FIELD_NAME,
27+
RULE_REFERENCE_FIELD_NAME,
2628
SIGNAL_RULE_NAME_FIELD_NAME,
2729
} from './constants';
30+
import { renderRuleName, renderEventModule, renderRulReference } from './formatted_field_helpers';
2831

2932
// simple black-list to prevent dragging and dropping fields such as message name
3033
const columnNamesNotDraggable = [MESSAGE_FIELD_NAME];
@@ -88,6 +91,12 @@ const FormattedFieldValueComponent: React.FC<{
8891
return (
8992
<Bytes contextId={contextId} eventId={eventId} fieldName={fieldName} value={`${value}`} />
9093
);
94+
} else if (fieldName === SIGNAL_RULE_NAME_FIELD_NAME) {
95+
return renderRuleName({ contextId, eventId, fieldName, linkValue, truncate, value });
96+
} else if (fieldName === EVENT_MODULE_FIELD_NAME) {
97+
return renderEventModule({ contextId, eventId, fieldName, linkValue, truncate, value });
98+
} else if (fieldName === RULE_REFERENCE_FIELD_NAME) {
99+
return renderRulReference({ contextId, eventId, fieldName, linkValue, truncate, value });
91100
} else if (columnNamesNotDraggable.includes(fieldName)) {
92101
return truncate && !isEmpty(value) ? (
93102
<TruncatableText data-test-subj="truncatable-message">
@@ -110,24 +119,6 @@ const FormattedFieldValueComponent: React.FC<{
110119
) : (
111120
<>{value}</>
112121
);
113-
} else if (fieldName === SIGNAL_RULE_NAME_FIELD_NAME) {
114-
const ruleName = `${value}`;
115-
const ruleId = linkValue;
116-
117-
return isString(value) && ruleName.length > 0 && ruleId != null ? (
118-
<DefaultDraggable
119-
field={fieldName}
120-
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${ruleId}`}
121-
tooltipContent={value}
122-
value={value}
123-
>
124-
<EuiLink href={getRuleDetailsUrl(ruleId)}>
125-
<TruncatableText data-test-subj="draggable-truncatable-content">{value}</TruncatableText>
126-
</EuiLink>
127-
</DefaultDraggable>
128-
) : (
129-
getEmptyTagValue()
130-
);
131122
} else {
132123
const contentValue = getOrEmptyTagFromValue(value);
133124
const content = truncate ? <TruncatableText>{contentValue}</TruncatableText> : contentValue;
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { EuiLink, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui';
8+
import { isString, isEmpty } from 'lodash/fp';
9+
import React from 'react';
10+
11+
import { DefaultDraggable } from '../../../draggables';
12+
import { getEmptyTagValue } from '../../../empty_value';
13+
import { getRuleDetailsUrl } from '../../../link_to/redirect_to_detection_engine';
14+
import { TruncatableText } from '../../../truncatable_text';
15+
16+
import { isUrlInvalid } from '../../../../pages/detection_engine/rules/components/step_about_rule/helpers';
17+
import endPointSvg from '../../../../utils/logo_endpoint/64_color.svg';
18+
19+
import * as i18n from './translations';
20+
21+
export const renderRuleName = ({
22+
contextId,
23+
eventId,
24+
fieldName,
25+
linkValue,
26+
truncate,
27+
value,
28+
}: {
29+
contextId: string;
30+
eventId: string;
31+
fieldName: string;
32+
linkValue: string | null | undefined;
33+
truncate?: boolean;
34+
value: string | number | null | undefined;
35+
}) => {
36+
const ruleName = `${value}`;
37+
const ruleId = linkValue;
38+
39+
const content = truncate ? <TruncatableText>{value}</TruncatableText> : value;
40+
41+
return isString(value) && ruleName.length > 0 && ruleId != null ? (
42+
<DefaultDraggable
43+
field={fieldName}
44+
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${ruleId}`}
45+
tooltipContent={value}
46+
value={value}
47+
>
48+
<EuiLink href={getRuleDetailsUrl(ruleId)}>{content}</EuiLink>
49+
</DefaultDraggable>
50+
) : (
51+
getEmptyTagValue()
52+
);
53+
};
54+
55+
export const renderEventModule = ({
56+
contextId,
57+
eventId,
58+
fieldName,
59+
linkValue,
60+
truncate,
61+
value,
62+
}: {
63+
contextId: string;
64+
eventId: string;
65+
fieldName: string;
66+
linkValue: string | null | undefined;
67+
truncate?: boolean;
68+
value: string | number | null | undefined;
69+
}) => {
70+
const moduleName = `${value}`;
71+
const endpointRefUrl = linkValue;
72+
73+
const content = truncate ? <TruncatableText>{value}</TruncatableText> : value;
74+
75+
return isString(value) && moduleName.length > 0 ? (
76+
<EuiFlexGroup
77+
gutterSize="none"
78+
alignItems="center"
79+
justifyContent={
80+
endpointRefUrl != null && !isEmpty(endpointRefUrl) ? 'flexStart' : 'spaceBetween'
81+
}
82+
>
83+
<EuiFlexItem>
84+
<DefaultDraggable
85+
field={fieldName}
86+
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${moduleName}`}
87+
tooltipContent={value}
88+
value={value}
89+
>
90+
{content}
91+
</DefaultDraggable>
92+
</EuiFlexItem>
93+
{endpointRefUrl != null &&
94+
!isEmpty(endpointRefUrl) &&
95+
!isUrlInvalid(endpointRefUrl) &&
96+
endpointRefUrl.includes('/alerts/') && (
97+
<EuiFlexItem grow={false}>
98+
<EuiToolTip
99+
data-test-subj="event-module-link-to-elastic-endpoint-security"
100+
content={
101+
<>
102+
<p>{i18n.LINK_ELASTIC_ENDPOINT_SECURITY}</p>
103+
<p>{endpointRefUrl}</p>
104+
</>
105+
}
106+
>
107+
<EuiLink href={endpointRefUrl} target="_blank">
108+
<EuiIcon type={endPointSvg} size="m" />
109+
</EuiLink>
110+
</EuiToolTip>
111+
</EuiFlexItem>
112+
)}
113+
</EuiFlexGroup>
114+
) : (
115+
getEmptyTagValue()
116+
);
117+
};
118+
119+
export const renderRulReference = ({
120+
contextId,
121+
eventId,
122+
fieldName,
123+
linkValue,
124+
truncate,
125+
value,
126+
}: {
127+
contextId: string;
128+
eventId: string;
129+
fieldName: string;
130+
linkValue: string | null | undefined;
131+
truncate?: boolean;
132+
value: string | number | null | undefined;
133+
}) => {
134+
const referenceUrlName = `${value}`;
135+
136+
const content = truncate ? <TruncatableText>{value}</TruncatableText> : value;
137+
138+
return isString(value) && referenceUrlName.length > 0 ? (
139+
<DefaultDraggable
140+
field={fieldName}
141+
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${referenceUrlName}`}
142+
tooltipContent={value}
143+
value={value}
144+
>
145+
{!isUrlInvalid(referenceUrlName) && (
146+
<EuiLink target="_blank" href={referenceUrlName}>
147+
{content}
148+
</EuiLink>
149+
)}
150+
{isUrlInvalid(referenceUrlName) && <>{content}</>}
151+
</DefaultDraggable>
152+
) : (
153+
getEmptyTagValue()
154+
);
155+
};

x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/translations.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,10 @@ export const IN = i18n.translate('xpack.siem.auditd.inDescription', {
2929
export const NON_EXISTENT = i18n.translate('xpack.siem.auditd.nonExistentDescription', {
3030
defaultMessage: 'an unknown process',
3131
});
32+
33+
export const LINK_ELASTIC_ENDPOINT_SECURITY = i18n.translate(
34+
'xpack.siem.event.module.linkToElasticEndpointSecurityDescription',
35+
{
36+
defaultMessage: 'Open in Elastic Endpoint Security',
37+
}
38+
);

x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ describe('Footer Timeline Component', () => {
121121
.find('[data-test-subj="TimelineMoreButton"]')
122122
.dive()
123123
.text();
124-
expect(loadButton).toContain('Load More');
124+
expect(loadButton).toContain('Load more');
125125
});
126126

127127
test('it does NOT render the loadMore button because there is nothing else to fetch', () => {

x-pack/legacy/plugins/siem/public/components/timeline/footer/translations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const LOADING = i18n.translate('xpack.siem.footer.loadingLabel', {
2727
});
2828

2929
export const LOAD_MORE = i18n.translate('xpack.siem.footer.loadMoreLabel', {
30-
defaultMessage: 'Load More',
30+
defaultMessage: 'Load more',
3131
});
3232

3333
export const TOTAL_COUNT_OF_EVENTS = i18n.translate('xpack.siem.footer.totalCountOfEvents', {

x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ export const useQuerySignals = <Hit, Aggs>(
4545
useEffect(() => {
4646
let isSubscribed = true;
4747
const abortCtrl = new AbortController();
48-
setLoading(true);
4948

5049
async function fetchData() {
5150
try {
51+
setLoading(true);
5252
const signalResponse = await fetchQuerySignals<Hit, Aggs>({
5353
query,
5454
signal: abortCtrl.signal,

x-pack/legacy/plugins/siem/public/containers/timeline/index.gql_query.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ export const timelineQuery = gql`
134134
name
135135
ip
136136
}
137+
rule {
138+
reference
139+
}
137140
source {
138141
bytes
139142
ip

x-pack/legacy/plugins/siem/public/graphql/introspection.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3985,6 +3985,14 @@
39853985
"isDeprecated": false,
39863986
"deprecationReason": null
39873987
},
3988+
{
3989+
"name": "rule",
3990+
"description": "",
3991+
"args": [],
3992+
"type": { "kind": "OBJECT", "name": "RuleEcsField", "ofType": null },
3993+
"isDeprecated": false,
3994+
"deprecationReason": null
3995+
},
39883996
{
39893997
"name": "signal",
39903998
"description": "",
@@ -4743,6 +4751,25 @@
47434751
"enumValues": null,
47444752
"possibleTypes": null
47454753
},
4754+
{
4755+
"kind": "OBJECT",
4756+
"name": "RuleEcsField",
4757+
"description": "",
4758+
"fields": [
4759+
{
4760+
"name": "reference",
4761+
"description": "",
4762+
"args": [],
4763+
"type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null },
4764+
"isDeprecated": false,
4765+
"deprecationReason": null
4766+
}
4767+
],
4768+
"inputFields": null,
4769+
"interfaces": [],
4770+
"enumValues": null,
4771+
"possibleTypes": null
4772+
},
47464773
{
47474774
"kind": "OBJECT",
47484775
"name": "SignalField",

0 commit comments

Comments
 (0)