Skip to content

Commit

Permalink
[SIEM] Add link to endpoint app through reference.url (#56211) (#56250)
Browse files Browse the repository at this point in the history
* add rule.reference

* Fix Load more

* Fix spacing

* Fix loading on hist graph detections

* add tooltip
  • Loading branch information
XavierM authored Jan 29, 2020
1 parent b652c96 commit 893c01a
Show file tree
Hide file tree
Showing 17 changed files with 287 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const alertsHeaders: ColumnHeader[] = [
columnHeaderType: defaultColumnHeaderType,
id: 'event.module',
width: DEFAULT_COLUMN_MIN_WIDTH,
linkField: 'rule.reference',
},
{
columnHeaderType: defaultColumnHeaderType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export const DATE_FIELD_TYPE = 'date';
export const HOST_NAME_FIELD_NAME = 'host.name';
export const IP_FIELD_TYPE = 'ip';
export const MESSAGE_FIELD_NAME = 'message';
export const EVENT_MODULE_FIELD_NAME = 'event.module';
export const RULE_REFERENCE_FIELD_NAME = 'rule.reference';
export const SIGNAL_RULE_NAME_FIELD_NAME = 'signal.rule.name';
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiLink } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
import { isNumber, isString, isEmpty } from 'lodash/fp';
import React from 'react';

Expand All @@ -15,16 +15,19 @@ import { getOrEmptyTagFromValue, getEmptyTagValue } from '../../../empty_value';
import { FormattedDate } from '../../../formatted_date';
import { FormattedIp } from '../../../formatted_ip';
import { HostDetailsLink } from '../../../links';
import { getRuleDetailsUrl } from '../../../link_to/redirect_to_detection_engine';

import { Port, PORT_NAMES } from '../../../port';
import { TruncatableText } from '../../../truncatable_text';
import {
DATE_FIELD_TYPE,
HOST_NAME_FIELD_NAME,
IP_FIELD_TYPE,
MESSAGE_FIELD_NAME,
EVENT_MODULE_FIELD_NAME,
RULE_REFERENCE_FIELD_NAME,
SIGNAL_RULE_NAME_FIELD_NAME,
} from './constants';
import { renderRuleName, renderEventModule, renderRulReference } from './formatted_field_helpers';

// simple black-list to prevent dragging and dropping fields such as message name
const columnNamesNotDraggable = [MESSAGE_FIELD_NAME];
Expand Down Expand Up @@ -88,6 +91,12 @@ const FormattedFieldValueComponent: React.FC<{
return (
<Bytes contextId={contextId} eventId={eventId} fieldName={fieldName} value={`${value}`} />
);
} else if (fieldName === SIGNAL_RULE_NAME_FIELD_NAME) {
return renderRuleName({ contextId, eventId, fieldName, linkValue, truncate, value });
} else if (fieldName === EVENT_MODULE_FIELD_NAME) {
return renderEventModule({ contextId, eventId, fieldName, linkValue, truncate, value });
} else if (fieldName === RULE_REFERENCE_FIELD_NAME) {
return renderRulReference({ contextId, eventId, fieldName, linkValue, truncate, value });
} else if (columnNamesNotDraggable.includes(fieldName)) {
return truncate && !isEmpty(value) ? (
<TruncatableText data-test-subj="truncatable-message">
Expand All @@ -110,24 +119,6 @@ const FormattedFieldValueComponent: React.FC<{
) : (
<>{value}</>
);
} else if (fieldName === SIGNAL_RULE_NAME_FIELD_NAME) {
const ruleName = `${value}`;
const ruleId = linkValue;

return isString(value) && ruleName.length > 0 && ruleId != null ? (
<DefaultDraggable
field={fieldName}
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${ruleId}`}
tooltipContent={value}
value={value}
>
<EuiLink href={getRuleDetailsUrl(ruleId)}>
<TruncatableText data-test-subj="draggable-truncatable-content">{value}</TruncatableText>
</EuiLink>
</DefaultDraggable>
) : (
getEmptyTagValue()
);
} else {
const contentValue = getOrEmptyTagFromValue(value);
const content = truncate ? <TruncatableText>{contentValue}</TruncatableText> : contentValue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiLink, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui';
import { isString, isEmpty } from 'lodash/fp';
import React from 'react';

import { DefaultDraggable } from '../../../draggables';
import { getEmptyTagValue } from '../../../empty_value';
import { getRuleDetailsUrl } from '../../../link_to/redirect_to_detection_engine';
import { TruncatableText } from '../../../truncatable_text';

import { isUrlInvalid } from '../../../../pages/detection_engine/rules/components/step_about_rule/helpers';
import endPointSvg from '../../../../utils/logo_endpoint/64_color.svg';

import * as i18n from './translations';

export const renderRuleName = ({
contextId,
eventId,
fieldName,
linkValue,
truncate,
value,
}: {
contextId: string;
eventId: string;
fieldName: string;
linkValue: string | null | undefined;
truncate?: boolean;
value: string | number | null | undefined;
}) => {
const ruleName = `${value}`;
const ruleId = linkValue;

const content = truncate ? <TruncatableText>{value}</TruncatableText> : value;

return isString(value) && ruleName.length > 0 && ruleId != null ? (
<DefaultDraggable
field={fieldName}
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${ruleId}`}
tooltipContent={value}
value={value}
>
<EuiLink href={getRuleDetailsUrl(ruleId)}>{content}</EuiLink>
</DefaultDraggable>
) : (
getEmptyTagValue()
);
};

export const renderEventModule = ({
contextId,
eventId,
fieldName,
linkValue,
truncate,
value,
}: {
contextId: string;
eventId: string;
fieldName: string;
linkValue: string | null | undefined;
truncate?: boolean;
value: string | number | null | undefined;
}) => {
const moduleName = `${value}`;
const endpointRefUrl = linkValue;

const content = truncate ? <TruncatableText>{value}</TruncatableText> : value;

return isString(value) && moduleName.length > 0 ? (
<EuiFlexGroup
gutterSize="none"
alignItems="center"
justifyContent={
endpointRefUrl != null && !isEmpty(endpointRefUrl) ? 'flexStart' : 'spaceBetween'
}
>
<EuiFlexItem>
<DefaultDraggable
field={fieldName}
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${moduleName}`}
tooltipContent={value}
value={value}
>
{content}
</DefaultDraggable>
</EuiFlexItem>
{endpointRefUrl != null &&
!isEmpty(endpointRefUrl) &&
!isUrlInvalid(endpointRefUrl) &&
endpointRefUrl.includes('/alerts/') && (
<EuiFlexItem grow={false}>
<EuiToolTip
data-test-subj="event-module-link-to-elastic-endpoint-security"
content={
<>
<p>{i18n.LINK_ELASTIC_ENDPOINT_SECURITY}</p>
<p>{endpointRefUrl}</p>
</>
}
>
<EuiLink href={endpointRefUrl} target="_blank">
<EuiIcon type={endPointSvg} size="m" />
</EuiLink>
</EuiToolTip>
</EuiFlexItem>
)}
</EuiFlexGroup>
) : (
getEmptyTagValue()
);
};

export const renderRulReference = ({
contextId,
eventId,
fieldName,
linkValue,
truncate,
value,
}: {
contextId: string;
eventId: string;
fieldName: string;
linkValue: string | null | undefined;
truncate?: boolean;
value: string | number | null | undefined;
}) => {
const referenceUrlName = `${value}`;

const content = truncate ? <TruncatableText>{value}</TruncatableText> : value;

return isString(value) && referenceUrlName.length > 0 ? (
<DefaultDraggable
field={fieldName}
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${referenceUrlName}`}
tooltipContent={value}
value={value}
>
{!isUrlInvalid(referenceUrlName) && (
<EuiLink target="_blank" href={referenceUrlName}>
{content}
</EuiLink>
)}
{isUrlInvalid(referenceUrlName) && <>{content}</>}
</DefaultDraggable>
) : (
getEmptyTagValue()
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,10 @@ export const IN = i18n.translate('xpack.siem.auditd.inDescription', {
export const NON_EXISTENT = i18n.translate('xpack.siem.auditd.nonExistentDescription', {
defaultMessage: 'an unknown process',
});

export const LINK_ELASTIC_ENDPOINT_SECURITY = i18n.translate(
'xpack.siem.event.module.linkToElasticEndpointSecurityDescription',
{
defaultMessage: 'Open in Elastic Endpoint Security',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ describe('Footer Timeline Component', () => {
.find('[data-test-subj="TimelineMoreButton"]')
.dive()
.text();
expect(loadButton).toContain('Load More');
expect(loadButton).toContain('Load more');
});

test('it does NOT render the loadMore button because there is nothing else to fetch', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const LOADING = i18n.translate('xpack.siem.footer.loadingLabel', {
});

export const LOAD_MORE = i18n.translate('xpack.siem.footer.loadMoreLabel', {
defaultMessage: 'Load More',
defaultMessage: 'Load more',
});

export const TOTAL_COUNT_OF_EVENTS = i18n.translate('xpack.siem.footer.totalCountOfEvents', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ export const useQuerySignals = <Hit, Aggs>(
useEffect(() => {
let isSubscribed = true;
const abortCtrl = new AbortController();
setLoading(true);

async function fetchData() {
try {
setLoading(true);
const signalResponse = await fetchQuerySignals<Hit, Aggs>({
query,
signal: abortCtrl.signal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ export const timelineQuery = gql`
name
ip
}
rule {
reference
}
source {
bytes
ip
Expand Down
27 changes: 27 additions & 0 deletions x-pack/legacy/plugins/siem/public/graphql/introspection.json
Original file line number Diff line number Diff line change
Expand Up @@ -3985,6 +3985,14 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "rule",
"description": "",
"args": [],
"type": { "kind": "OBJECT", "name": "RuleEcsField", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "signal",
"description": "",
Expand Down Expand Up @@ -4743,6 +4751,25 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "RuleEcsField",
"description": "",
"fields": [
{
"name": "reference",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SignalField",
Expand Down
18 changes: 16 additions & 2 deletions x-pack/legacy/plugins/siem/public/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,8 @@ export interface Ecs {

network?: Maybe<NetworkEcsField>;

rule?: Maybe<RuleEcsField>;

signal?: Maybe<SignalField>;

source?: Maybe<SourceEcsFields>;
Expand Down Expand Up @@ -970,6 +972,10 @@ export interface NetworkEcsField {
transport?: Maybe<string[]>;
}

export interface RuleEcsField {
reference?: Maybe<string[]>;
}

export interface SignalField {
rule?: Maybe<RuleField>;

Expand Down Expand Up @@ -4456,6 +4462,8 @@ export namespace GetTimelineQuery {

host: Maybe<Host>;

rule: Maybe<Rule>;

source: Maybe<_Source>;

destination: Maybe<Destination>;
Expand Down Expand Up @@ -4671,6 +4679,12 @@ export namespace GetTimelineQuery {
ip: Maybe<string[]>;
};

export type Rule = {
__typename?: 'RuleEcsField';

reference: Maybe<string[]>;
};

export type _Source = {
__typename?: 'SourceEcsFields';

Expand Down Expand Up @@ -4792,10 +4806,10 @@ export namespace GetTimelineQuery {

original_time: Maybe<string[]>;

rule: Maybe<Rule>;
rule: Maybe<_Rule>;
};

export type Rule = {
export type _Rule = {
__typename?: 'RuleField';

id: Maybe<string[]>;
Expand Down
Loading

0 comments on commit 893c01a

Please sign in to comment.