From 6da1cb1495cdb24dd5a1d9ab6500f3baff068698 Mon Sep 17 00:00:00 2001
From: PhilippeOberti
Date: Fri, 26 Aug 2022 08:11:34 +0200
Subject: [PATCH 1/8] [TIP] Investigate in timeline
- add investigate in timeline component using basic redux actions duplicated from Security Solution plugin
- investigates in timeline adds threat indicator as well as source entries using mapping
---
.../public/threat_intelligence/routes.tsx | 5 +
.../common/types/indicator.ts | 29 ++++
.../indicators_table/actions_row_cell.tsx | 17 ++-
.../components/indicators_table/styles.ts | 18 +++
.../modules/timeline/actions/actions.ts | 31 ++++
.../public/modules/timeline/actions/index.ts | 10 ++
.../modules/timeline/actions/selectors.ts | 25 ++++
.../public/modules/timeline/actions/types.ts | 14 ++
.../add_to_timeline/add_to_timeline.tsx | 2 +-
.../add_to_timeline/{index.tsx => index.ts} | 0
.../components/add_to_timeline/styles.ts | 4 +-
.../investigate_in_timeline/index.ts | 8 ++
.../investigate_in_timeline.stories.tsx | 19 +++
.../investigate_in_timeline.test.tsx | 38 +++++
.../investigate_in_timeline.tsx | 136 ++++++++++++++++++
.../investigate_in_timeline/styles.ts | 18 +++
.../modules/timeline/constants/constants.ts | 12 ++
.../timeline/constants/default_headers.ts | 55 +++++++
.../modules/timeline/constants/defaults.ts | 73 ++++++++++
.../modules/timeline/constants/index.ts | 10 ++
.../threat_intelligence/public/plugin.tsx | 25 ++--
.../threat_intelligence/public/types.ts | 7 +-
22 files changed, 537 insertions(+), 19 deletions(-)
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/styles.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/actions/actions.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/actions/index.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/actions/selectors.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/actions/types.ts
rename x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/{index.tsx => index.ts} (100%)
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/index.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/styles.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/constants/constants.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/constants/default_headers.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/constants/defaults.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/constants/index.ts
diff --git a/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx b/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx
index 3fd03ff39abf1..391bfa4482c18 100644
--- a/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx
+++ b/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx
@@ -11,6 +11,8 @@ import { TrackApplicationView } from '@kbn/usage-collection-plugin/public';
import type { SecuritySolutionPluginContext } from '@kbn/threat-intelligence-plugin/public';
import { THREAT_INTELLIGENCE_BASE_PATH } from '@kbn/threat-intelligence-plugin/public';
import type { SourcererDataView } from '@kbn/threat-intelligence-plugin/public/types';
+import type { Store } from 'redux';
+import { getStore } from '../common/store';
import { useKibana } from '../common/lib/kibana';
import { FiltersGlobal } from '../common/components/filters_global';
import { SpyRoute } from '../common/utils/route/spy_routes';
@@ -32,11 +34,14 @@ const ThreatIntelligence = memo(() => {
return ;
}
+ const securitySolutionStore = getStore() as Store;
+
const securitySolutionContext: SecuritySolutionPluginContext = {
getFiltersGlobalComponent: () => FiltersGlobal,
getPageWrapper: () => SecuritySolutionPageWrapper,
licenseService,
sourcererDataView: sourcererDataView as unknown as SourcererDataView,
+ getSecuritySolutionStore: securitySolutionStore,
};
return (
diff --git a/x-pack/plugins/threat_intelligence/common/types/indicator.ts b/x-pack/plugins/threat_intelligence/common/types/indicator.ts
index 2edcbb5a829ea..d3bc97027e0e8 100644
--- a/x-pack/plugins/threat_intelligence/common/types/indicator.ts
+++ b/x-pack/plugins/threat_intelligence/common/types/indicator.ts
@@ -37,8 +37,10 @@ export enum RawIndicatorFieldId {
FileImphash = 'threat.indicator.file.imphash',
FilePehash = 'threat.indicator.file.pehash',
FileVhash = 'threat.indicator.file.vhash',
+ FileTelfhash = 'threat.indicator.file.elf.telfhash',
X509Serial = 'threat.indicator.x509.serial_number',
WindowsRegistryKey = 'threat.indicator.registry.key',
+ WindowsRegistryPath = 'threat.indicator.registry.path',
AutonomousSystemNumber = 'threat.indicator.as.number',
MacAddress = 'threat.indicator.mac',
TimeStamp = '@timestamp',
@@ -47,6 +49,33 @@ export enum RawIndicatorFieldId {
NameOrigin = 'threat.indicator.name_origin',
}
+export const IndicatorFieldEventEnrichmentMap: { [id: string]: string[] } = {
+ [RawIndicatorFieldId.FileMd5]: ['file.hash.md5', 'threat.enrichments.indicator.file.hash.md5'],
+ [RawIndicatorFieldId.FileSha1]: ['file.hash.sha1', 'threat.enrichments.indicator.file.hash.sha1'],
+ [RawIndicatorFieldId.FileSha256]: [
+ 'file.hash.sha256',
+ 'threat.enrichments.indicator.file.hash.sha256',
+ ],
+ [RawIndicatorFieldId.FileImphash]: [
+ 'file.pe.imphash',
+ 'threat.enrichments.indicator.file.pe.imphash',
+ ],
+ [RawIndicatorFieldId.FileTelfhash]: [
+ 'file.elf.telfhash',
+ 'threat.enrichments.indicator.file.elf.telfhash',
+ ],
+ [RawIndicatorFieldId.FileSSDeep]: [
+ 'file.hash.ssdeep',
+ 'threat.enrichments.indicator.file.hash.ssdeep',
+ ],
+ [RawIndicatorFieldId.Ip]: ['source.ip', 'destination.ip', 'threat.enrichments.indicator.ip'],
+ [RawIndicatorFieldId.UrlFull]: ['url.full', 'threat.enrichments.indicator.url.full'],
+ [RawIndicatorFieldId.WindowsRegistryPath]: [
+ 'registry.path',
+ 'threat.enrichments.indicator.registry.path',
+ ],
+};
+
/**
* Threat Intelligence Indicator interface.
*/
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx
index d2f1379bb8320..12fe49772e6fe 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx
@@ -6,11 +6,15 @@
*/
import React, { useContext, VFC } from 'react';
+import { InvestigateInTimeline } from '../../../timeline/components/investigate_in_timeline';
import { Indicator } from '../../../../../common/types/indicator';
import { OpenIndicatorFlyoutButton } from '../open_indicator_flyout_button/open_indicator_flyout_button';
import { IndicatorsTableContext } from './context';
+import { useStyles } from './styles';
export const ActionsRowCell: VFC<{ indicator: Indicator }> = ({ indicator }) => {
+ const styles = useStyles();
+
const indicatorTableContext = useContext(IndicatorsTableContext);
if (!indicatorTableContext) {
@@ -20,10 +24,13 @@ export const ActionsRowCell: VFC<{ indicator: Indicator }> = ({ indicator }) =>
const { setExpanded, expanded } = indicatorTableContext;
return (
-
+
+
+
+
);
};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/styles.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/styles.ts
new file mode 100644
index 0000000000000..3ae7bf4ef16d9
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/styles.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { CSSObject } from '@emotion/react';
+
+export const useStyles = () => {
+ const rowActionsDiv: CSSObject = {
+ display: 'flex',
+ };
+
+ return {
+ rowActionsDiv,
+ };
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/actions.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/actions.ts
new file mode 100644
index 0000000000000..29d14e6d1c47f
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/actions.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import actionCreatorFactory from 'typescript-fsa';
+import { DataProvider } from '@kbn/timelines-plugin/common';
+
+// Sourcerer actions
+
+const sourcererActionCreator = actionCreatorFactory('x-pack/security_solution/local/sourcerer');
+
+export const setSelectedDataView = sourcererActionCreator('SET_SELECTED_DATA_VIEW');
+
+// Timeline actions
+
+const timelineActionCreator = actionCreatorFactory('x-pack/security_solution/local/timeline');
+
+export interface Timeline {
+ columns: unknown[];
+ dataProviders: DataProvider[];
+ dataViewId: string;
+ id: string;
+ indexNames: string[];
+ show: boolean;
+ timelineType: string;
+}
+
+export const createTimeline = timelineActionCreator('CREATE_TIMELINE');
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/index.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/index.ts
new file mode 100644
index 0000000000000..3f7f7d560cf17
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './actions';
+export * from './selectors';
+export * from './types';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/selectors.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/selectors.ts
new file mode 100644
index 0000000000000..c1dac017d2984
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/selectors.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { TimelineById } from '@kbn/timelines-plugin/public/types';
+import { createSelector } from 'reselect';
+import { TimelineModel } from '@kbn/security-solution-plugin/public';
+import { State } from './types';
+
+// Timeline selectors
+
+const selectTimelineById = (state: State): TimelineById => state.timeline.timelineById;
+
+export const timelineByIdSelector = createSelector(
+ selectTimelineById,
+ (timelineById) => timelineById
+);
+
+export const selectTimeline = (state: State, timelineId: string): TimelineModel =>
+ state.timeline.timelineById[timelineId];
+
+export const getTimelineByIdSelector = () => createSelector(selectTimeline, (timeline) => timeline);
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/types.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/types.ts
new file mode 100644
index 0000000000000..5f56e05f54548
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/types.ts
@@ -0,0 +1,14 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { TimelineState } from '@kbn/security-solution-plugin/public/timelines/store/timeline/types';
+
+export interface TimelinePluginState {
+ timeline: TimelineState;
+}
+
+export type State = TimelinePluginState;
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
index c677a7613e3e6..be5b7fa0fe618 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
@@ -81,7 +81,7 @@ export const AddToTimeline: VFC = ({ data, field, component,
if (component) addToTimelineProps.Component = component;
return (
-
+
{addToTimelineButton(addToTimelineProps)}
);
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/index.ts
similarity index 100%
rename from x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/index.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/index.ts
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/styles.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/styles.ts
index 4f7814eb793cc..cc1b166edbc76 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/styles.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/styles.ts
@@ -8,11 +8,11 @@
import { CSSObject } from '@emotion/react';
export const useStyles = () => {
- const button: CSSObject = {
+ const inlineFlex: CSSObject = {
display: 'inline-flex',
};
return {
- button,
+ inlineFlex,
};
};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/index.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/index.ts
new file mode 100644
index 0000000000000..34bd1d7d56277
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './investigate_in_timeline';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx
new file mode 100644
index 0000000000000..38643afabb35f
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { Story } from '@storybook/react';
+import { InvestigateInTimeline } from './investigate_in_timeline';
+
+export default {
+ component: InvestigateInTimeline,
+ title: 'InvestigateInTimeline',
+};
+
+export const Default: Story
= () => {
+ return <>>;
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx
new file mode 100644
index 0000000000000..d586fb7e2a8dd
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { render } from '@testing-library/react';
+import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator';
+import { EMPTY_VALUE } from '../../../../../common/constants';
+import { TestProvidersComponent } from '../../../../common/mocks/test_providers';
+import { InvestigateInTimeline } from './investigate_in_timeline';
+
+describe('', () => {
+ it('should render timeline button when Indicator data', () => {
+ const mockData: Indicator = generateMockIndicator();
+
+ const component = render(
+
+
+
+ );
+ expect(component).toMatchSnapshot();
+ });
+
+ it(`should render empty component when calculated value is ${EMPTY_VALUE}`, () => {
+ const mockData: Indicator = generateMockIndicator();
+ mockData.fields['threat.indicator.first_seen'] = [''];
+
+ const component = render(
+
+
+
+ );
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx
new file mode 100644
index 0000000000000..8c6a135999564
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx
@@ -0,0 +1,136 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { VFC } from 'react';
+import { DataProvider, QueryOperator } from '@kbn/timelines-plugin/common';
+import { useDispatch } from 'react-redux';
+import { EuiButtonIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { IN_ICON_TEST_ID } from '../../../query_bar/components/filter_in_out';
+import { createTimeline, setSelectedDataView } from '../../actions';
+import { getIndicatorFieldAndValue } from '../../../indicators/lib/field_value';
+import {
+ Indicator,
+ IndicatorFieldEventEnrichmentMap,
+ RawIndicatorFieldId,
+} from '../../../../../common/types/indicator';
+import { EMPTY_VALUE } from '../../../../../common/constants';
+import { useStyles } from './styles';
+
+export interface AddToTimelineProps {
+ /**
+ * Value passed to the timeline. Used in combination with field if is type of {@link Indicator}.
+ */
+ data: Indicator;
+ /**
+ * Used as `data-test-subj` value for e2e tests.
+ */
+ testId?: string;
+}
+
+/**
+ * Investigate in timeline button, supports being passed a {@link Indicator}.
+ * This implementation uses the InvestigateInTimelineAction component (x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx)
+ * retrieved from the SecuritySolutionContext.
+ *
+ * @returns add to timeline button or an empty component.
+ */
+export const InvestigateInTimeline: VFC = ({ data, testId }) => {
+ const styles = useStyles();
+ const dispatch = useDispatch();
+
+ const { key, value } = getIndicatorFieldAndValue(data, RawIndicatorFieldId.Name);
+
+ if (!value || value === EMPTY_VALUE || !key) {
+ return <>>;
+ }
+
+ const operator = ':' as QueryOperator;
+ const dataProviders: DataProvider[] = [
+ {
+ and: [],
+ enabled: true,
+ id: `timeline-indicator-${key}-${value}`,
+ name: value,
+ excluded: false,
+ kqlQuery: '',
+ queryMatch: {
+ field: key,
+ value,
+ operator,
+ },
+ },
+ ];
+
+ const eventEnrichments: string[] = IndicatorFieldEventEnrichmentMap[key];
+ if (eventEnrichments) {
+ eventEnrichments.forEach((eventEnrichment: string) => {
+ dataProviders.push({
+ and: [],
+ enabled: true,
+ id: `timeline-indicator-${eventEnrichment}-${value}`,
+ name: eventEnrichment,
+ excluded: false,
+ kqlQuery: '',
+ queryMatch: {
+ field: eventEnrichment,
+ value,
+ operator,
+ },
+ });
+ });
+ }
+
+ // remove previous provider added to the timeline
+ const onClick = () => {
+ dispatch(
+ setSelectedDataView({
+ id: 'timeline',
+ selectedDataViewId: 'security-solution-default',
+ selectedPatterns: ['filebeat-*'],
+ })
+ );
+
+ dispatch(
+ createTimeline({
+ columns: [
+ {
+ columnHeaderType: 'not-filtered',
+ id: '@timestamp',
+ initialWidth: 190,
+ type: 'date',
+ },
+ ],
+ dataViewId: 'security-solution-default',
+ dataProviders,
+ id: 'timeline-1',
+ indexNames: ['filebeat-*'],
+ show: true,
+ timelineType: 'default',
+ })
+ );
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/styles.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/styles.ts
new file mode 100644
index 0000000000000..cc1b166edbc76
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/styles.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { CSSObject } from '@emotion/react';
+
+export const useStyles = () => {
+ const inlineFlex: CSSObject = {
+ display: 'inline-flex',
+ };
+
+ return {
+ inlineFlex,
+ };
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/constants.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/constants.ts
new file mode 100644
index 0000000000000..a9062f2506f79
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/constants.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/** The default minimum width of a column (when a width for the column type is not specified) */
+export const DEFAULT_COLUMN_MIN_WIDTH = 180; // px
+
+/** The default minimum width of a column of type `date` */
+export const DEFAULT_DATE_COLUMN_MIN_WIDTH = 190; // px
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/default_headers.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/default_headers.ts
new file mode 100644
index 0000000000000..f6eb0bc279c37
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/default_headers.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_DATE_COLUMN_MIN_WIDTH } from '.';
+
+export const defaultColumnHeaderType = 'not-filtered';
+
+export const defaultHeaders: unknown[] = [
+ {
+ columnHeaderType: defaultColumnHeaderType,
+ id: '@timestamp',
+ initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH,
+ esTypes: ['date'],
+ type: 'date',
+ },
+ {
+ columnHeaderType: defaultColumnHeaderType,
+ id: 'message',
+ initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
+ },
+ {
+ columnHeaderType: defaultColumnHeaderType,
+ id: 'event.category',
+ initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
+ },
+ {
+ columnHeaderType: defaultColumnHeaderType,
+ id: 'event.action',
+ initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
+ },
+ {
+ columnHeaderType: defaultColumnHeaderType,
+ id: 'host.name',
+ initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
+ },
+ {
+ columnHeaderType: defaultColumnHeaderType,
+ id: 'source.ip',
+ initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
+ },
+ {
+ columnHeaderType: defaultColumnHeaderType,
+ id: 'destination.ip',
+ initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
+ },
+ {
+ columnHeaderType: defaultColumnHeaderType,
+ id: 'user.name',
+ initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
+ },
+];
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/defaults.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/defaults.ts
new file mode 100644
index 0000000000000..0d0c65c68f484
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/defaults.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { defaultHeaders } from '.';
+
+export const timelineDefaults = {
+ activeTab: 'query',
+ prevActiveTab: 'query',
+ columns: defaultHeaders,
+ documentType: '',
+ defaultColumns: defaultHeaders,
+ dataProviders: [],
+ dataViewId: null,
+ dateRange: { from: '2022-09-06T09:53:30.557Z', end: '2022-09-07T09:53:30.557Z' },
+ deletedEventIds: [],
+ description: '',
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: '',
+ timestampField: '@timestamp',
+ query: '',
+ size: 100,
+ },
+ eventType: 'all',
+ eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
+ expandedDetail: {},
+ highlightedDropAndProviderId: '',
+ historyIds: [],
+ filters: [],
+ indexNames: [],
+ isFavorite: false,
+ isLive: false,
+ isSelectAllChecked: false,
+ isLoading: false,
+ isSaving: false,
+ itemsPerPage: 25,
+ itemsPerPageOptions: [10, 25, 50, 100],
+ kqlMode: 'filter',
+ kqlQuery: {
+ filterQuery: null,
+ },
+ loadingEventIds: [],
+ resolveTimelineConfig: undefined,
+ queryFields: [],
+ title: '',
+ timelineType: 'default',
+ templateTimelineId: null,
+ templateTimelineVersion: null,
+ noteIds: [],
+ pinnedEventIds: {},
+ pinnedEventsSaveObject: {},
+ savedObjectId: null,
+ selectAll: false,
+ selectedEventIds: {},
+ sessionViewConfig: null,
+ show: false,
+ showCheckboxes: false,
+ sort: [
+ {
+ columnId: '@timestamp',
+ columnType: 'date',
+ esTypes: ['date'],
+ sortDirection: 'desc',
+ },
+ ],
+ status: 'draft',
+ version: null,
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/index.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/index.ts
new file mode 100644
index 0000000000000..9e49013c62023
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './constants';
+export * from './defaults';
+export * from './default_headers';
diff --git a/x-pack/plugins/threat_intelligence/public/plugin.tsx b/x-pack/plugins/threat_intelligence/public/plugin.tsx
index 60e5dc2e2a07c..0ec3064c5052b 100755
--- a/x-pack/plugins/threat_intelligence/public/plugin.tsx
+++ b/x-pack/plugins/threat_intelligence/public/plugin.tsx
@@ -7,6 +7,7 @@
import { CoreStart, Plugin } from '@kbn/core/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
+import { Provider as ReduxStoreProvider } from 'react-redux';
import React, { Suspense, VFC } from 'react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { KibanaContextProvider } from './hooks/use_kibana';
@@ -46,15 +47,17 @@ export const createApp =
({ securitySolutionContext }: AppProps) =>
(
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
@@ -77,7 +80,9 @@ export class ThreatIntelligencePlugin implements Plugin {
...plugins,
} as Services;
- return { getComponent: createApp(services) };
+ return {
+ getComponent: createApp(services),
+ };
}
public stop() {}
diff --git a/x-pack/plugins/threat_intelligence/public/types.ts b/x-pack/plugins/threat_intelligence/public/types.ts
index 266ed215bd852..24a5f2bd2a656 100644
--- a/x-pack/plugins/threat_intelligence/public/types.ts
+++ b/x-pack/plugins/threat_intelligence/public/types.ts
@@ -18,6 +18,7 @@ import { TimelinesUIStart } from '@kbn/timelines-plugin/public';
import type { TriggersAndActionsUIPublicPluginStart as TriggersActionsStart } from '@kbn/triggers-actions-ui-plugin/public';
import { BrowserField } from '@kbn/triggers-actions-ui-plugin/public/application/sections/field_browser/types';
import { DataViewBase } from '@kbn/es-query';
+import { Store } from 'redux';
export interface SecuritySolutionDataViewBase extends DataViewBase {
fields: Array;
@@ -77,7 +78,11 @@ export interface SecuritySolutionPluginContext {
*/
licenseService: LicenseAware;
/**
- * Gets Security Solution shared information like browerFields, indexPattern and selectedPatterns in DataView
+ * Gets Security Solution shared information like browerFields, indexPattern and selectedPatterns in DataView.
*/
sourcererDataView: SourcererDataView;
+ /**
+ * Security Solution store
+ */
+ getSecuritySolutionStore: Store;
}
From 00c752860999a07c84c85c195af12d9698d84445 Mon Sep 17 00:00:00 2001
From: PhilippeOberti
Date: Mon, 12 Sep 2022 14:03:12 +0200
Subject: [PATCH 2/8] [TIP] Second implementation of investigate in timeline
- remove first option as well as actions, selectors and types
- add new investigate in timeline hook in Security Solution plugin and pass via context to TI plugin
- replace UrlOriginal by UrlFull in the threat.indicator.name mapping
---
.../public/threat_intelligence/routes.tsx | 2 +
.../threat_intelligence/translations.ts} | 13 +-
.../use_investigate_in_timeline.ts | 130 ++++++++++++++++++
.../modules/indicators/lib/display_name.ts | 2 +-
.../modules/timeline/actions/actions.ts | 31 -----
.../public/modules/timeline/actions/index.ts | 10 --
.../modules/timeline/actions/selectors.ts | 25 ----
.../investigate_in_timeline.tsx | 47 ++-----
.../modules/timeline/constants/constants.ts | 12 --
.../timeline/constants/default_headers.ts | 55 --------
.../modules/timeline/constants/defaults.ts | 73 ----------
.../modules/timeline/constants/index.ts | 10 --
.../threat_intelligence/public/types.ts | 15 ++
13 files changed, 169 insertions(+), 256 deletions(-)
rename x-pack/plugins/{threat_intelligence/public/modules/timeline/actions/types.ts => security_solution/public/threat_intelligence/translations.ts} (54%)
create mode 100644 x-pack/plugins/security_solution/public/threat_intelligence/use_investigate_in_timeline.ts
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/actions/actions.ts
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/actions/index.ts
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/actions/selectors.ts
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/constants/constants.ts
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/constants/default_headers.ts
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/constants/defaults.ts
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/constants/index.ts
diff --git a/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx b/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx
index 391bfa4482c18..ab197f60236c6 100644
--- a/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx
+++ b/x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx
@@ -12,6 +12,7 @@ import type { SecuritySolutionPluginContext } from '@kbn/threat-intelligence-plu
import { THREAT_INTELLIGENCE_BASE_PATH } from '@kbn/threat-intelligence-plugin/public';
import type { SourcererDataView } from '@kbn/threat-intelligence-plugin/public/types';
import type { Store } from 'redux';
+import { useInvestigateInTimeline } from './use_investigate_in_timeline';
import { getStore } from '../common/store';
import { useKibana } from '../common/lib/kibana';
import { FiltersGlobal } from '../common/components/filters_global';
@@ -42,6 +43,7 @@ const ThreatIntelligence = memo(() => {
licenseService,
sourcererDataView: sourcererDataView as unknown as SourcererDataView,
getSecuritySolutionStore: securitySolutionStore,
+ getUseInvestigateInTimeline: useInvestigateInTimeline,
};
return (
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/types.ts b/x-pack/plugins/security_solution/public/threat_intelligence/translations.ts
similarity index 54%
rename from x-pack/plugins/threat_intelligence/public/modules/timeline/actions/types.ts
rename to x-pack/plugins/security_solution/public/threat_intelligence/translations.ts
index 5f56e05f54548..d795abda3e95e 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/types.ts
+++ b/x-pack/plugins/security_solution/public/threat_intelligence/translations.ts
@@ -5,10 +5,11 @@
* 2.0.
*/
-import { TimelineState } from '@kbn/security-solution-plugin/public/timelines/store/timeline/types';
+import { i18n } from '@kbn/i18n';
-export interface TimelinePluginState {
- timeline: TimelineState;
-}
-
-export type State = TimelinePluginState;
+export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate(
+ 'xpack.threatIntelligence.investigateInTimelineTitle',
+ {
+ defaultMessage: 'Investigate in timeline',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/threat_intelligence/use_investigate_in_timeline.ts b/x-pack/plugins/security_solution/public/threat_intelligence/use_investigate_in_timeline.ts
new file mode 100644
index 0000000000000..882246be3e095
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/threat_intelligence/use_investigate_in_timeline.ts
@@ -0,0 +1,130 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useCallback, useMemo } from 'react';
+import { useDispatch } from 'react-redux';
+import { timelineDefaults } from '../timelines/store/timeline/defaults';
+import { APP_UI_ID } from '../../common/constants';
+import type { DataProvider } from '../../common/types';
+import { TimelineId, TimelineType } from '../../common/types';
+import { useDeepEqualSelector } from '../common/hooks/use_selector';
+import { useKibana } from '../common/lib/kibana';
+import { useStartTransaction } from '../common/lib/apm/use_start_transaction';
+import { timelineActions, timelineSelectors } from '../timelines/store/timeline';
+import { useCreateTimeline } from '../timelines/components/timeline/properties/use_create_timeline';
+import type { CreateTimelineProps } from '../detections/components/alerts_table/types';
+import { dispatchUpdateTimeline } from '../timelines/components/open_timeline/helpers';
+
+interface UseInvestigateInTimelineActionProps {
+ /**
+ * Created when the user clicks on the Investigate in Timeline button.
+ * DataProvider contain the field(s) and value(s) displayed in the timeline.
+ */
+ dataProviders: DataProvider[];
+ /**
+ * Start date used in the createTimeline method.
+ */
+ from: string;
+ /**
+ * End date used in the createTimeline method.
+ */
+ to: string;
+}
+
+/**
+ * Hook passed down to the Threat Intelligence plugin, via context.
+ * This code is closely duplicated from here: https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx,
+ * the main changes being:
+ * - no exceptions are handled at the moment
+ * - we use dataProviders, from and to directly instead of consuming ecsData
+ */
+export const useInvestigateInTimeline = ({
+ dataProviders,
+ from,
+ to,
+}: UseInvestigateInTimelineActionProps) => {
+ const {
+ data: { query },
+ } = useKibana().services;
+ const dispatch = useDispatch();
+ const { startTransaction } = useStartTransaction();
+
+ const filterManagerBackup = useMemo(() => query.filterManager, [query.filterManager]);
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const { filterManager: activeFilterManager } = useDeepEqualSelector((state) =>
+ getManageTimeline(state, TimelineId.active ?? '')
+ );
+ const filterManager = useMemo(
+ () => activeFilterManager ?? filterManagerBackup,
+ [activeFilterManager, filterManagerBackup]
+ );
+
+ const updateTimelineIsLoading = useCallback(
+ (payload) => dispatch(timelineActions.updateIsLoading(payload)),
+ [dispatch]
+ );
+
+ const clearActiveTimeline = useCreateTimeline({
+ timelineId: TimelineId.active,
+ timelineType: TimelineType.default,
+ });
+
+ const createTimeline = useCallback(
+ ({ from: fromTimeline, timeline, to: toTimeline, ruleNote }: CreateTimelineProps) => {
+ clearActiveTimeline();
+ updateTimelineIsLoading({ id: TimelineId.active, isLoading: false });
+ dispatchUpdateTimeline(dispatch)({
+ duplicate: true,
+ from: fromTimeline,
+ id: TimelineId.active,
+ notes: [],
+ timeline: {
+ ...timeline,
+ filterManager,
+ indexNames: timeline.indexNames ?? [],
+ show: true,
+ },
+ to: toTimeline,
+ ruleNote,
+ })();
+ },
+ [dispatch, filterManager, updateTimelineIsLoading, clearActiveTimeline]
+ );
+
+ const investigateInTimelineClick = useCallback(async () => {
+ startTransaction({ name: `${APP_UI_ID} threat indicator investigateInTimeline` });
+ await createTimeline({
+ from,
+ notes: null,
+ timeline: {
+ ...timelineDefaults,
+ dataProviders,
+ id: TimelineId.active,
+ indexNames: [],
+ dateRange: {
+ start: from,
+ end: to,
+ },
+ eventType: 'all',
+ filters: [],
+ kqlQuery: {
+ filterQuery: {
+ kuery: {
+ kind: 'kuery',
+ expression: '',
+ },
+ serializedQuery: '',
+ },
+ },
+ },
+ to,
+ ruleNote: '',
+ });
+ }, [startTransaction, createTimeline, dataProviders, from, to]);
+
+ return investigateInTimelineClick;
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.ts
index 1bd1ae0584cc2..7795d08259d32 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.ts
@@ -42,7 +42,7 @@ const mappingsArray: Mappings = [
RawIndicatorFieldId.FileVhash,
],
],
- [['url'], [RawIndicatorFieldId.UrlOriginal]],
+ [['url'], [RawIndicatorFieldId.UrlFull]],
[['domain', 'domain-name'], [RawIndicatorFieldId.UrlDomain]],
[['x509-certificate', 'x509 serial'], [RawIndicatorFieldId.X509Serial]],
[['email-addr'], [RawIndicatorFieldId.EmailAddress]],
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/actions.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/actions.ts
deleted file mode 100644
index 29d14e6d1c47f..0000000000000
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/actions.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import actionCreatorFactory from 'typescript-fsa';
-import { DataProvider } from '@kbn/timelines-plugin/common';
-
-// Sourcerer actions
-
-const sourcererActionCreator = actionCreatorFactory('x-pack/security_solution/local/sourcerer');
-
-export const setSelectedDataView = sourcererActionCreator('SET_SELECTED_DATA_VIEW');
-
-// Timeline actions
-
-const timelineActionCreator = actionCreatorFactory('x-pack/security_solution/local/timeline');
-
-export interface Timeline {
- columns: unknown[];
- dataProviders: DataProvider[];
- dataViewId: string;
- id: string;
- indexNames: string[];
- show: boolean;
- timelineType: string;
-}
-
-export const createTimeline = timelineActionCreator('CREATE_TIMELINE');
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/index.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/index.ts
deleted file mode 100644
index 3f7f7d560cf17..0000000000000
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export * from './actions';
-export * from './selectors';
-export * from './types';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/selectors.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/selectors.ts
deleted file mode 100644
index c1dac017d2984..0000000000000
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/actions/selectors.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { TimelineById } from '@kbn/timelines-plugin/public/types';
-import { createSelector } from 'reselect';
-import { TimelineModel } from '@kbn/security-solution-plugin/public';
-import { State } from './types';
-
-// Timeline selectors
-
-const selectTimelineById = (state: State): TimelineById => state.timeline.timelineById;
-
-export const timelineByIdSelector = createSelector(
- selectTimelineById,
- (timelineById) => timelineById
-);
-
-export const selectTimeline = (state: State, timelineId: string): TimelineModel =>
- state.timeline.timelineById[timelineId];
-
-export const getTimelineByIdSelector = () => createSelector(selectTimeline, (timeline) => timeline);
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx
index 8c6a135999564..1c47c38cb97bc 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx
@@ -5,13 +5,14 @@
* 2.0.
*/
-import React, { VFC } from 'react';
+import React, { useContext, VFC } from 'react';
+import moment from 'moment';
import { DataProvider, QueryOperator } from '@kbn/timelines-plugin/common';
-import { useDispatch } from 'react-redux';
import { EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { unwrapValue } from '../../../indicators/lib/unwrap_value';
+import { SecuritySolutionContext } from '../../../../containers/security_solution_context';
import { IN_ICON_TEST_ID } from '../../../query_bar/components/filter_in_out';
-import { createTimeline, setSelectedDataView } from '../../actions';
import { getIndicatorFieldAndValue } from '../../../indicators/lib/field_value';
import {
Indicator,
@@ -41,7 +42,8 @@ export interface AddToTimelineProps {
*/
export const InvestigateInTimeline: VFC = ({ data, testId }) => {
const styles = useStyles();
- const dispatch = useDispatch();
+
+ const securitySolutionContext = useContext(SecuritySolutionContext);
const { key, value } = getIndicatorFieldAndValue(data, RawIndicatorFieldId.Name);
@@ -85,35 +87,14 @@ export const InvestigateInTimeline: VFC = ({ data, testId })
});
}
- // remove previous provider added to the timeline
- const onClick = () => {
- dispatch(
- setSelectedDataView({
- id: 'timeline',
- selectedDataViewId: 'security-solution-default',
- selectedPatterns: ['filebeat-*'],
- })
- );
+ const to = unwrapValue(data, RawIndicatorFieldId.TimeStamp) as string;
+ const from = moment(to).subtract(10, 'm').toISOString();
- dispatch(
- createTimeline({
- columns: [
- {
- columnHeaderType: 'not-filtered',
- id: '@timestamp',
- initialWidth: 190,
- type: 'date',
- },
- ],
- dataViewId: 'security-solution-default',
- dataProviders,
- id: 'timeline-1',
- indexNames: ['filebeat-*'],
- show: true,
- timelineType: 'default',
- })
- );
- };
+ const investigateInTimelineClick = securitySolutionContext?.getUseInvestigateInTimeline({
+ dataProviders,
+ from,
+ to,
+ });
return (
@@ -129,7 +110,7 @@ export const InvestigateInTimeline: VFC
= ({ data, testId })
iconSize="s"
size="xs"
color="primary"
- onClick={onClick}
+ onClick={investigateInTimelineClick}
/>
);
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/constants.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/constants.ts
deleted file mode 100644
index a9062f2506f79..0000000000000
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/constants.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/** The default minimum width of a column (when a width for the column type is not specified) */
-export const DEFAULT_COLUMN_MIN_WIDTH = 180; // px
-
-/** The default minimum width of a column of type `date` */
-export const DEFAULT_DATE_COLUMN_MIN_WIDTH = 190; // px
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/default_headers.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/default_headers.ts
deleted file mode 100644
index f6eb0bc279c37..0000000000000
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/default_headers.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_DATE_COLUMN_MIN_WIDTH } from '.';
-
-export const defaultColumnHeaderType = 'not-filtered';
-
-export const defaultHeaders: unknown[] = [
- {
- columnHeaderType: defaultColumnHeaderType,
- id: '@timestamp',
- initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH,
- esTypes: ['date'],
- type: 'date',
- },
- {
- columnHeaderType: defaultColumnHeaderType,
- id: 'message',
- initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
- },
- {
- columnHeaderType: defaultColumnHeaderType,
- id: 'event.category',
- initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
- },
- {
- columnHeaderType: defaultColumnHeaderType,
- id: 'event.action',
- initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
- },
- {
- columnHeaderType: defaultColumnHeaderType,
- id: 'host.name',
- initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
- },
- {
- columnHeaderType: defaultColumnHeaderType,
- id: 'source.ip',
- initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
- },
- {
- columnHeaderType: defaultColumnHeaderType,
- id: 'destination.ip',
- initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
- },
- {
- columnHeaderType: defaultColumnHeaderType,
- id: 'user.name',
- initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
- },
-];
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/defaults.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/defaults.ts
deleted file mode 100644
index 0d0c65c68f484..0000000000000
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/defaults.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { defaultHeaders } from '.';
-
-export const timelineDefaults = {
- activeTab: 'query',
- prevActiveTab: 'query',
- columns: defaultHeaders,
- documentType: '',
- defaultColumns: defaultHeaders,
- dataProviders: [],
- dataViewId: null,
- dateRange: { from: '2022-09-06T09:53:30.557Z', end: '2022-09-07T09:53:30.557Z' },
- deletedEventIds: [],
- description: '',
- eqlOptions: {
- eventCategoryField: 'event.category',
- tiebreakerField: '',
- timestampField: '@timestamp',
- query: '',
- size: 100,
- },
- eventType: 'all',
- eventIdToNoteIds: {},
- excludedRowRendererIds: [],
- expandedDetail: {},
- highlightedDropAndProviderId: '',
- historyIds: [],
- filters: [],
- indexNames: [],
- isFavorite: false,
- isLive: false,
- isSelectAllChecked: false,
- isLoading: false,
- isSaving: false,
- itemsPerPage: 25,
- itemsPerPageOptions: [10, 25, 50, 100],
- kqlMode: 'filter',
- kqlQuery: {
- filterQuery: null,
- },
- loadingEventIds: [],
- resolveTimelineConfig: undefined,
- queryFields: [],
- title: '',
- timelineType: 'default',
- templateTimelineId: null,
- templateTimelineVersion: null,
- noteIds: [],
- pinnedEventIds: {},
- pinnedEventsSaveObject: {},
- savedObjectId: null,
- selectAll: false,
- selectedEventIds: {},
- sessionViewConfig: null,
- show: false,
- showCheckboxes: false,
- sort: [
- {
- columnId: '@timestamp',
- columnType: 'date',
- esTypes: ['date'],
- sortDirection: 'desc',
- },
- ],
- status: 'draft',
- version: null,
-};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/index.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/index.ts
deleted file mode 100644
index 9e49013c62023..0000000000000
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/constants/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export * from './constants';
-export * from './defaults';
-export * from './default_headers';
diff --git a/x-pack/plugins/threat_intelligence/public/types.ts b/x-pack/plugins/threat_intelligence/public/types.ts
index 24a5f2bd2a656..acca5c6356ca9 100644
--- a/x-pack/plugins/threat_intelligence/public/types.ts
+++ b/x-pack/plugins/threat_intelligence/public/types.ts
@@ -19,6 +19,7 @@ import type { TriggersAndActionsUIPublicPluginStart as TriggersActionsStart } fr
import { BrowserField } from '@kbn/triggers-actions-ui-plugin/public/application/sections/field_browser/types';
import { DataViewBase } from '@kbn/es-query';
import { Store } from 'redux';
+import { DataProvider } from '@kbn/timelines-plugin/common';
export interface SecuritySolutionDataViewBase extends DataViewBase {
fields: Array;
@@ -59,6 +60,12 @@ export interface SourcererDataView {
loading: boolean;
}
+export interface UseInvestigateInTimelineProps {
+ dataProviders: DataProvider[];
+ from: string;
+ to: string;
+}
+
/**
* Methods exposed from the security solution to the threat intelligence application.
*/
@@ -85,4 +92,12 @@ export interface SecuritySolutionPluginContext {
* Security Solution store
*/
getSecuritySolutionStore: Store;
+ /**
+ * Pass UseInvestigateInTimeline functionality to TI plugin
+ */
+ getUseInvestigateInTimeline: ({
+ dataProviders,
+ from,
+ to,
+ }: UseInvestigateInTimelineProps) => () => Promise;
}
From 891c409df0763581344731341bc90f70604e506e Mon Sep 17 00:00:00 2001
From: PhilippeOberti
Date: Wed, 14 Sep 2022 17:41:35 +0200
Subject: [PATCH 3/8] [TIP] Separate Investigate In Timeline in 2 components
- split InvestigateInTimeline into 2 components (one for Button display the other for ButtonIcon)
- extract most of the logic into useInvestigateInTimeline hook
- add unit and e2e tests
- remove threat.enrichments entries in the map
---
.../common/types/indicator.ts | 39 ++---
.../cypress/e2e/timeline.cy.ts | 16 ++
.../cypress/screens/indicators.ts | 6 +
.../common/mocks/mock_security_context.tsx | 8 +
.../indicators_flyout/indicators_flyout.tsx | 15 ++
.../indicators_table/actions_row_cell.tsx | 6 +-
.../indicators/lib/display_name.test.ts | 4 +-
.../add_to_timeline/add_to_timeline.tsx | 21 +--
.../investigate_in_timeline.stories.tsx | 19 ---
.../investigate_in_timeline.tsx | 117 -------------
...vestigate_in_timeline_button.test.tsx.snap | 155 +++++++++++++++++
.../investigate_in_timeline_button/index.ts | 8 +
...investigate_in_timeline_button.stories.tsx | 27 +++
.../investigate_in_timeline_button.test.tsx | 45 +++++
.../investigate_in_timeline_button.tsx | 46 +++++
...gate_in_timeline_button_icon.test.tsx.snap | 159 ++++++++++++++++++
.../index.ts | 8 +
...tigate_in_timeline_button_icon.stories.tsx | 27 +++
...estigate_in_timeline_button_icon.test.tsx} | 22 ++-
.../investigate_in_timeline_button_icon.tsx | 63 +++++++
.../styles.ts | 0
.../index.ts | 2 +-
.../use_investigate_in_timeline.test.tsx | 41 +++++
.../hooks/use_investigate_in_timeline.ts | 65 +++++++
.../timeline/lib/data-provider.test.ts | 20 +++
.../modules/timeline/lib/data-provider.ts | 32 ++++
26 files changed, 780 insertions(+), 191 deletions(-)
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/__snapshots__/investigate_in_timeline_button.test.tsx.snap
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/index.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.stories.tsx
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.test.tsx
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.tsx
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/__snapshots__/investigate_in_timeline_button_icon.test.tsx.snap
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/index.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.stories.tsx
rename x-pack/plugins/threat_intelligence/public/modules/timeline/components/{investigate_in_timeline/investigate_in_timeline.test.tsx => investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.test.tsx} (60%)
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.tsx
rename x-pack/plugins/threat_intelligence/public/modules/timeline/components/{investigate_in_timeline => investigate_in_timeline_button_icon}/styles.ts (100%)
rename x-pack/plugins/threat_intelligence/public/modules/timeline/{components/investigate_in_timeline => hooks}/index.ts (84%)
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.test.tsx
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.test.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.ts
diff --git a/x-pack/plugins/threat_intelligence/common/types/indicator.ts b/x-pack/plugins/threat_intelligence/common/types/indicator.ts
index 4f7cf9795375a..823809a7750a0 100644
--- a/x-pack/plugins/threat_intelligence/common/types/indicator.ts
+++ b/x-pack/plugins/threat_intelligence/common/types/indicator.ts
@@ -51,31 +51,20 @@ export enum RawIndicatorFieldId {
NameOrigin = 'threat.indicator.name_origin',
}
+/**
+ * Threat indicator field map to Enriched Event.
+ * (reverse of https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/common/cti/constants.ts#L35)
+ */
export const IndicatorFieldEventEnrichmentMap: { [id: string]: string[] } = {
- [RawIndicatorFieldId.FileMd5]: ['file.hash.md5', 'threat.enrichments.indicator.file.hash.md5'],
- [RawIndicatorFieldId.FileSha1]: ['file.hash.sha1', 'threat.enrichments.indicator.file.hash.sha1'],
- [RawIndicatorFieldId.FileSha256]: [
- 'file.hash.sha256',
- 'threat.enrichments.indicator.file.hash.sha256',
- ],
- [RawIndicatorFieldId.FileImphash]: [
- 'file.pe.imphash',
- 'threat.enrichments.indicator.file.pe.imphash',
- ],
- [RawIndicatorFieldId.FileTelfhash]: [
- 'file.elf.telfhash',
- 'threat.enrichments.indicator.file.elf.telfhash',
- ],
- [RawIndicatorFieldId.FileSSDeep]: [
- 'file.hash.ssdeep',
- 'threat.enrichments.indicator.file.hash.ssdeep',
- ],
- [RawIndicatorFieldId.Ip]: ['source.ip', 'destination.ip', 'threat.enrichments.indicator.ip'],
- [RawIndicatorFieldId.UrlFull]: ['url.full', 'threat.enrichments.indicator.url.full'],
- [RawIndicatorFieldId.WindowsRegistryPath]: [
- 'registry.path',
- 'threat.enrichments.indicator.registry.path',
- ],
+ [RawIndicatorFieldId.FileMd5]: ['file.hash.md5'],
+ [RawIndicatorFieldId.FileSha1]: ['file.hash.sha1'],
+ [RawIndicatorFieldId.FileSha256]: ['file.hash.sha256'],
+ [RawIndicatorFieldId.FileImphash]: ['file.pe.imphash'],
+ [RawIndicatorFieldId.FileTelfhash]: ['file.elf.telfhash'],
+ [RawIndicatorFieldId.FileSSDeep]: ['file.hash.ssdeep'],
+ [RawIndicatorFieldId.Ip]: ['source.ip', 'destination.ip'],
+ [RawIndicatorFieldId.UrlFull]: ['url.full'],
+ [RawIndicatorFieldId.WindowsRegistryPath]: ['registry.path'],
};
/**
@@ -122,7 +111,7 @@ export const generateMockUrlIndicator = (): Indicator => {
indicator.fields['threat.indicator.url.full'] = ['https://0.0.0.0/test'];
indicator.fields['threat.indicator.url.original'] = ['https://0.0.0.0/test'];
indicator.fields['threat.indicator.name'] = ['https://0.0.0.0/test'];
- indicator.fields['threat.indicator.name_origin'] = ['threat.indicator.url.original'];
+ indicator.fields['threat.indicator.name_origin'] = ['threat.indicator.url.full'];
return indicator;
};
diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts
index 9077ef0fe4980..de3d0adf72c81 100644
--- a/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts
+++ b/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts
@@ -19,6 +19,8 @@ import {
UNTITLED_TIMELINE_BUTTON,
FLYOUT_OVERVIEW_TAB_BLOCKS_TIMELINE_BUTTON,
FLYOUT_OVERVIEW_TAB_BLOCKS_ITEM,
+ INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON,
+ INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON,
} from '../screens/indicators';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { login } from '../tasks/login';
@@ -88,5 +90,19 @@ describe('Indicators', () => {
cy.get(UNTITLED_TIMELINE_BUTTON).should('exist').first().click();
cy.get(TIMELINE_DRAGGABLE_ITEM).should('exist');
});
+
+ it('should investigate in timeline when clicking in an indicator table action row', () => {
+ cy.get(INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON).should('exist').first().click();
+ cy.get(UNTITLED_TIMELINE_BUTTON).should('exist').first().click();
+ cy.get(TIMELINE_DRAGGABLE_ITEM).should('exist');
+ });
+
+ it('should investigate in timeline when clicking in an indicator flyout', () => {
+ cy.get(TOGGLE_FLYOUT_BUTTON).first().click({ force: true });
+ cy.get(INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON).should('exist').first().click();
+ cy.get(FLYOUT_CLOSE_BUTTON).should('exist').click();
+ cy.get(UNTITLED_TIMELINE_BUTTON).should('exist').first().click();
+ cy.get(TIMELINE_DRAGGABLE_ITEM).should('exist');
+ });
});
});
diff --git a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts
index b9f982105696e..4ceaa0c020e99 100644
--- a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts
+++ b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts
@@ -101,3 +101,9 @@ export const UNTITLED_TIMELINE_BUTTON = '[data-test-subj="flyoutOverlay"]';
export const TIMELINE_DRAGGABLE_ITEM = '[data-test-subj="providerContainer"]';
export const KQL_FILTER = '[id="popoverFor_filter0"]';
+
+export const INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON =
+ '[data-test-subj="tiIndicatorTableInvestigateInTimelineButtonIcon"]';
+
+export const INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON =
+ '[data-test-subj="tiIndicatorFlyoutInvestigateInTimelineButton"]';
diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/mock_security_context.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/mock_security_context.tsx
index 87ffc300fef13..cef2598510da0 100644
--- a/x-pack/plugins/threat_intelligence/public/common/mocks/mock_security_context.tsx
+++ b/x-pack/plugins/threat_intelligence/public/common/mocks/mock_security_context.tsx
@@ -28,4 +28,12 @@ export const getSecuritySolutionContextMock = (): SecuritySolutionPluginContext
indexPattern: { fields: [], title: '' },
loading: false,
},
+ getSecuritySolutionStore: {
+ // @ts-ignore
+ dispatch: () => jest.fn(),
+ },
+ getUseInvestigateInTimeline:
+ ({ dataProviders, from, to }) =>
+ () =>
+ new Promise((resolve) => window.alert('investigate in timeline')),
});
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx
index 4026980ae1c89..303059c61fc69 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.tsx
@@ -7,8 +7,11 @@
import React, { useMemo, useState, VFC } from 'react';
import {
+ EuiFlexGroup,
+ EuiFlexItem,
EuiFlyout,
EuiFlyoutBody,
+ EuiFlyoutFooter,
EuiFlyoutHeader,
EuiSpacer,
EuiTab,
@@ -18,6 +21,7 @@ import {
useGeneratedHtmlId,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
+import { InvestigateInTimelineButton } from '../../../timeline/components/investigate_in_timeline_button';
import { DateFormatter } from '../../../../components/date_formatter/date_formatter';
import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indicator';
import { IndicatorsFlyoutJson } from './tabs/indicators_flyout_json/indicators_flyout_json';
@@ -28,6 +32,7 @@ import { IndicatorsFlyoutOverview } from './tabs/indicators_flyout_overview';
export const TITLE_TEST_ID = 'tiIndicatorFlyoutTitle';
export const SUBTITLE_TEST_ID = 'tiIndicatorFlyoutSubtitle';
export const TABS_TEST_ID = 'tiIndicatorFlyoutTabs';
+export const INVESTIGATE_IN_TIMELINE_BUTTON_ID = 'tiIndicatorFlyoutInvestigateInTimelineButton';
const enum TAB_IDS {
overview,
@@ -142,6 +147,16 @@ export const IndicatorsFlyout: VFC = ({ indicator, closeF
{selectedTabContent}
+
+
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx
index 12fe49772e6fe..277b64fcacc3d 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx
@@ -6,12 +6,14 @@
*/
import React, { useContext, VFC } from 'react';
-import { InvestigateInTimeline } from '../../../timeline/components/investigate_in_timeline';
+import { InvestigateInTimelineButtonIcon } from '../../../timeline/components/investigate_in_timeline_button_icon';
import { Indicator } from '../../../../../common/types/indicator';
import { OpenIndicatorFlyoutButton } from '../open_indicator_flyout_button/open_indicator_flyout_button';
import { IndicatorsTableContext } from './context';
import { useStyles } from './styles';
+const INVESTIGATE_TEST_ID = 'tiIndicatorTableInvestigateInTimelineButtonIcon';
+
export const ActionsRowCell: VFC<{ indicator: Indicator }> = ({ indicator }) => {
const styles = useStyles();
@@ -30,7 +32,7 @@ export const ActionsRowCell: VFC<{ indicator: Indicator }> = ({ indicator }) =>
onOpen={setExpanded}
isOpen={Boolean(expanded && expanded._id === indicator._id)}
/>
-
+
);
};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.test.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.test.ts
index 1a23b1b3819e0..cfc7f73a8d7f3 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.test.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/display_name.test.ts
@@ -33,7 +33,7 @@ describe('display name generation', () => {
if (doc['threat.indicator.file.pehash'].value!=null) { return emit(doc['threat.indicator.file.pehash'].value) }
if (doc['threat.indicator.file.vhash'].value!=null) { return emit(doc['threat.indicator.file.vhash'].value) } }
- if (doc['threat.indicator.type'].value != null && doc['threat.indicator.type'].value.toLowerCase()=='url') { if (doc['threat.indicator.url.original'].value!=null) { return emit(doc['threat.indicator.url.original'].value) } }
+ if (doc['threat.indicator.type'].value != null && doc['threat.indicator.type'].value.toLowerCase()=='url') { if (doc['threat.indicator.url.full'].value!=null) { return emit(doc['threat.indicator.url.full'].value) } }
if (doc['threat.indicator.type'].value != null && doc['threat.indicator.type'].value.toLowerCase()=='domain') { if (doc['threat.indicator.url.domain'].value!=null) { return emit(doc['threat.indicator.url.domain'].value) } }
if (doc['threat.indicator.type'].value != null && doc['threat.indicator.type'].value.toLowerCase()=='domain-name') { if (doc['threat.indicator.url.domain'].value!=null) { return emit(doc['threat.indicator.url.domain'].value) } }
@@ -83,7 +83,7 @@ describe('display name generation', () => {
if (doc['threat.indicator.file.pehash'].value!=null) { return emit('threat.indicator.file.pehash') }
if (doc['threat.indicator.file.vhash'].value!=null) { return emit('threat.indicator.file.vhash') } }
- if (doc['threat.indicator.type'].value != null && doc['threat.indicator.type'].value.toLowerCase()=='url') { if (doc['threat.indicator.url.original'].value!=null) { return emit('threat.indicator.url.original') } }
+ if (doc['threat.indicator.type'].value != null && doc['threat.indicator.type'].value.toLowerCase()=='url') { if (doc['threat.indicator.url.full'].value!=null) { return emit('threat.indicator.url.full') } }
if (doc['threat.indicator.type'].value != null && doc['threat.indicator.type'].value.toLowerCase()=='domain') { if (doc['threat.indicator.url.domain'].value!=null) { return emit('threat.indicator.url.domain') } }
if (doc['threat.indicator.type'].value != null && doc['threat.indicator.type'].value.toLowerCase()=='domain-name') { if (doc['threat.indicator.url.domain'].value!=null) { return emit('threat.indicator.url.domain') } }
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
index 792f1f2784b0e..f303c5d783021 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
@@ -6,10 +6,11 @@
*/
import React, { useRef, VFC } from 'react';
-import { DataProvider, QueryOperator } from '@kbn/timelines-plugin/common';
+import { DataProvider } from '@kbn/timelines-plugin/common';
import { AddToTimelineButtonProps } from '@kbn/timelines-plugin/public';
import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui/src/components/button';
import { EuiContextMenuItem } from '@elastic/eui';
+import { generateDataProvider } from '../../lib/data-provider';
import { ComponentType } from '../../../../../common/types/component_type';
import { getIndicatorFieldAndValue } from '../../../indicators/lib/field_value';
import { EMPTY_VALUE } from '../../../../../common/constants';
@@ -65,23 +66,7 @@ export const AddToTimeline: VFC = ({ data, field, type, as,
return <>>;
}
- const operator = ':' as QueryOperator;
-
- const dataProvider: DataProvider[] = [
- {
- and: [],
- enabled: true,
- id: `timeline-indicator-${key}-${value}`,
- name: value,
- excluded: false,
- kqlQuery: '',
- queryMatch: {
- field: key,
- value,
- operator,
- },
- },
- ];
+ const dataProvider: DataProvider[] = [generateDataProvider(key, value)];
const addToTimelineProps: AddToTimelineButtonProps = {
dataProvider,
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx
deleted file mode 100644
index 38643afabb35f..0000000000000
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { Story } from '@storybook/react';
-import { InvestigateInTimeline } from './investigate_in_timeline';
-
-export default {
- component: InvestigateInTimeline,
- title: 'InvestigateInTimeline',
-};
-
-export const Default: Story = () => {
- return <>>;
-};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx
deleted file mode 100644
index 1c47c38cb97bc..0000000000000
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useContext, VFC } from 'react';
-import moment from 'moment';
-import { DataProvider, QueryOperator } from '@kbn/timelines-plugin/common';
-import { EuiButtonIcon } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { unwrapValue } from '../../../indicators/lib/unwrap_value';
-import { SecuritySolutionContext } from '../../../../containers/security_solution_context';
-import { IN_ICON_TEST_ID } from '../../../query_bar/components/filter_in_out';
-import { getIndicatorFieldAndValue } from '../../../indicators/lib/field_value';
-import {
- Indicator,
- IndicatorFieldEventEnrichmentMap,
- RawIndicatorFieldId,
-} from '../../../../../common/types/indicator';
-import { EMPTY_VALUE } from '../../../../../common/constants';
-import { useStyles } from './styles';
-
-export interface AddToTimelineProps {
- /**
- * Value passed to the timeline. Used in combination with field if is type of {@link Indicator}.
- */
- data: Indicator;
- /**
- * Used as `data-test-subj` value for e2e tests.
- */
- testId?: string;
-}
-
-/**
- * Investigate in timeline button, supports being passed a {@link Indicator}.
- * This implementation uses the InvestigateInTimelineAction component (x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx)
- * retrieved from the SecuritySolutionContext.
- *
- * @returns add to timeline button or an empty component.
- */
-export const InvestigateInTimeline: VFC = ({ data, testId }) => {
- const styles = useStyles();
-
- const securitySolutionContext = useContext(SecuritySolutionContext);
-
- const { key, value } = getIndicatorFieldAndValue(data, RawIndicatorFieldId.Name);
-
- if (!value || value === EMPTY_VALUE || !key) {
- return <>>;
- }
-
- const operator = ':' as QueryOperator;
- const dataProviders: DataProvider[] = [
- {
- and: [],
- enabled: true,
- id: `timeline-indicator-${key}-${value}`,
- name: value,
- excluded: false,
- kqlQuery: '',
- queryMatch: {
- field: key,
- value,
- operator,
- },
- },
- ];
-
- const eventEnrichments: string[] = IndicatorFieldEventEnrichmentMap[key];
- if (eventEnrichments) {
- eventEnrichments.forEach((eventEnrichment: string) => {
- dataProviders.push({
- and: [],
- enabled: true,
- id: `timeline-indicator-${eventEnrichment}-${value}`,
- name: eventEnrichment,
- excluded: false,
- kqlQuery: '',
- queryMatch: {
- field: eventEnrichment,
- value,
- operator,
- },
- });
- });
- }
-
- const to = unwrapValue(data, RawIndicatorFieldId.TimeStamp) as string;
- const from = moment(to).subtract(10, 'm').toISOString();
-
- const investigateInTimelineClick = securitySolutionContext?.getUseInvestigateInTimeline({
- dataProviders,
- from,
- to,
- });
-
- return (
-
-
-
- );
-};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/__snapshots__/investigate_in_timeline_button.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/__snapshots__/investigate_in_timeline_button.test.tsx.snap
new file mode 100644
index 0000000000000..a01cfb51e66d4
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/__snapshots__/investigate_in_timeline_button.test.tsx.snap
@@ -0,0 +1,155 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render button when Indicator data is correct 1`] = `
+Object {
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+ ,
+ "container":
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
+
+exports[` should render empty component when Indicator data is incorrect 1`] = `
+Object {
+ "asFragment": [Function],
+ "baseElement":
+
+ ,
+ "container": ,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/index.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/index.ts
new file mode 100644
index 0000000000000..d562e618a664e
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './investigate_in_timeline_button';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.stories.tsx
new file mode 100644
index 0000000000000..c3c8e65001a3a
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.stories.tsx
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { Story } from '@storybook/react';
+import { StoryProvidersComponent } from '../../../../common/mocks/story_providers';
+import { generateMockUrlIndicator } from '../../../../../common/types/indicator';
+import { InvestigateInTimelineButton } from './investigate_in_timeline_button';
+
+export default {
+ component: InvestigateInTimelineButton,
+ title: 'InvestigateInTimelineButton',
+};
+
+const mockIndicator = generateMockUrlIndicator();
+
+export const Default: Story = () => {
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.test.tsx
new file mode 100644
index 0000000000000..b064ead7e645e
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.test.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { render } from '@testing-library/react';
+import {
+ generateMockIndicator,
+ generateMockUrlIndicator,
+ Indicator,
+} from '../../../../../common/types/indicator';
+import { TestProvidersComponent } from '../../../../common/mocks/test_providers';
+import { InvestigateInTimelineButton } from './investigate_in_timeline_button';
+
+describe('', () => {
+ it('should render button when Indicator data is correct', () => {
+ const mockData: Indicator = generateMockUrlIndicator();
+ const mockId = 'mockId';
+
+ const component = render(
+
+
+
+ );
+
+ expect(component.getByTestId(mockId)).toBeInTheDocument();
+ expect(component).toMatchSnapshot();
+ });
+
+ it('should render empty component when Indicator data is incorrect', () => {
+ const mockData: Indicator = generateMockIndicator();
+ mockData.fields['threat.indicator.first_seen'] = [''];
+
+ const component = render(
+
+
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.tsx
new file mode 100644
index 0000000000000..4791b507700f8
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button/investigate_in_timeline_button.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { VFC } from 'react';
+import { EuiButton } from '@elastic/eui';
+import { useInvestigateInTimeline } from '../../hooks/use_investigate_in_timeline';
+import { Indicator } from '../../../../../common/types/indicator';
+
+export interface InvestigateInTimelineButtonProps {
+ /**
+ * Value passed to the timeline. Used in combination with field if is type of {@link Indicator}.
+ */
+ data: Indicator;
+ /**
+ * Used for unit and e2e tests.
+ */
+ ['data-test-subj']?: string;
+}
+
+/**
+ * Investigate in timeline button, supports being passed a {@link Indicator}.
+ * This implementation uses the InvestigateInTimelineAction component (x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx)
+ * retrieved from the SecuritySolutionContext.
+ *
+ * @returns add to timeline button or an empty component.
+ */
+export const InvestigateInTimelineButton: VFC = ({
+ data,
+ ...props
+}) => {
+ const { onClick } = useInvestigateInTimeline({ indicator: data });
+
+ if (!onClick) {
+ return <>>;
+ }
+
+ return (
+
+ Investigate in Timeline
+
+ );
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/__snapshots__/investigate_in_timeline_button_icon.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/__snapshots__/investigate_in_timeline_button_icon.test.tsx.snap
new file mode 100644
index 0000000000000..a17cb9d47bb57
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/__snapshots__/investigate_in_timeline_button_icon.test.tsx.snap
@@ -0,0 +1,159 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render button icon when Indicator data is correct 1`] = `
+Object {
+ "asFragment": [Function],
+ "baseElement":
+
+ ,
+ "container": ,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
+
+exports[` should render empty component when calculated value is - 1`] = `
+Object {
+ "asFragment": [Function],
+ "baseElement":
+
+ ,
+ "container": ,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/index.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/index.ts
new file mode 100644
index 0000000000000..6ed30045b29b4
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './investigate_in_timeline_button_icon';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.stories.tsx
new file mode 100644
index 0000000000000..15c5bc0c23ed4
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.stories.tsx
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { Story } from '@storybook/react';
+import { StoryProvidersComponent } from '../../../../common/mocks/story_providers';
+import { generateMockUrlIndicator } from '../../../../../common/types/indicator';
+import { InvestigateInTimelineButtonIcon } from './investigate_in_timeline_button_icon';
+
+export default {
+ component: InvestigateInTimelineButtonIcon,
+ title: 'InvestigateInTimelineButtonIcon',
+};
+
+const mockIndicator = generateMockUrlIndicator();
+
+export const Default: Story = () => {
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.test.tsx
similarity index 60%
rename from x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.test.tsx
index d586fb7e2a8dd..3cdaa0528ca5e 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.test.tsx
@@ -7,20 +7,27 @@
import React from 'react';
import { render } from '@testing-library/react';
-import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator';
+import {
+ generateMockIndicator,
+ generateMockUrlIndicator,
+ Indicator,
+} from '../../../../../common/types/indicator';
import { EMPTY_VALUE } from '../../../../../common/constants';
import { TestProvidersComponent } from '../../../../common/mocks/test_providers';
-import { InvestigateInTimeline } from './investigate_in_timeline';
+import { InvestigateInTimelineButtonIcon } from './investigate_in_timeline_button_icon';
-describe('', () => {
- it('should render timeline button when Indicator data', () => {
- const mockData: Indicator = generateMockIndicator();
+describe('', () => {
+ it('should render button icon when Indicator data is correct', () => {
+ const mockData: Indicator = generateMockUrlIndicator();
+ const mockId = 'mockId';
const component = render(
-
+
);
+
+ expect(component.getByTestId(mockId)).toBeInTheDocument();
expect(component).toMatchSnapshot();
});
@@ -30,9 +37,10 @@ describe('', () => {
const component = render(
-
+
);
+
expect(component).toMatchSnapshot();
});
});
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.tsx
new file mode 100644
index 0000000000000..3585d0d2e62e6
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/investigate_in_timeline_button_icon.tsx
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { VFC } from 'react';
+import { EuiButtonIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { useInvestigateInTimeline } from '../../hooks/use_investigate_in_timeline';
+import { Indicator } from '../../../../../common/types/indicator';
+import { useStyles } from './styles';
+
+export interface InvestigateInTimelineButtonIconProps {
+ /**
+ * Value passed to the timeline. Used in combination with field if is type of {@link Indicator}.
+ */
+ data: Indicator;
+ /**
+ * Used for unit and e2e tests.
+ */
+ ['data-test-subj']?: string;
+}
+
+/**
+ * Investigate in timeline button, supports being passed a {@link Indicator}.
+ * This implementation uses the InvestigateInTimelineAction component (x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx)
+ * retrieved from the SecuritySolutionContext.
+ *
+ * @returns add to timeline button or an empty component.
+ */
+export const InvestigateInTimelineButtonIcon: VFC = ({
+ data,
+ ...props
+}) => {
+ const styles = useStyles();
+
+ const { onClick } = useInvestigateInTimeline({ indicator: data });
+
+ if (!onClick) {
+ return <>>;
+ }
+
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/styles.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/styles.ts
similarity index 100%
rename from x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/styles.ts
rename to x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/styles.ts
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/index.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/index.ts
similarity index 84%
rename from x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/index.ts
rename to x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/index.ts
index 34bd1d7d56277..b4e2c354c6df9 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/index.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export * from './investigate_in_timeline';
+export * from './use_investigate_in_timeline';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.test.tsx
new file mode 100644
index 0000000000000..cbc040a936dc3
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.test.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { renderHook, RenderHookResult, Renderer } from '@testing-library/react-hooks';
+import {
+ useInvestigateInTimeline,
+ UseInvestigateInTimelineValue,
+} from './use_investigate_in_timeline';
+import {
+ generateMockIndicator,
+ generateMockUrlIndicator,
+} from '../../../../common/types/indicator';
+import { TestProvidersComponent } from '../../../common/mocks/test_providers';
+
+describe('useInvestigateInTimeline()', () => {
+ let hookResult: RenderHookResult<{}, UseInvestigateInTimelineValue, Renderer>;
+
+ it('should return empty object if Indicator is incorrect', () => {
+ const indicator = generateMockIndicator();
+ indicator.fields['threat.indicator.name'] = ['wrong'];
+
+ hookResult = renderHook(() => useInvestigateInTimeline({ indicator }), {
+ wrapper: TestProvidersComponent,
+ });
+ expect(hookResult.result.current).toEqual({});
+ });
+
+ it('should return ', () => {
+ const indicator = generateMockUrlIndicator();
+
+ hookResult = renderHook(() => useInvestigateInTimeline({ indicator }), {
+ wrapper: TestProvidersComponent,
+ });
+
+ expect(hookResult.result.current).toHaveProperty('onClick');
+ });
+});
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts
new file mode 100644
index 0000000000000..06f4adb933167
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useContext } from 'react';
+import moment from 'moment';
+import { DataProvider } from '@kbn/timelines-plugin/common';
+import { generateDataProvider } from '../lib/data-provider';
+import { SecuritySolutionContext } from '../../../containers/security_solution_context';
+import { getIndicatorFieldAndValue } from '../../indicators/lib/field_value';
+import { unwrapValue } from '../../indicators/lib/unwrap_value';
+import { EMPTY_VALUE } from '../../../../common/constants';
+import {
+ Indicator,
+ IndicatorFieldEventEnrichmentMap,
+ RawIndicatorFieldId,
+} from '../../../../common/types/indicator';
+
+export interface UseInvestigateInTimelineParam {
+ /**
+ * Indicator used to retrieve the field and value then passed to the Investigate in Timeline logic
+ */
+ indicator: Indicator;
+}
+
+export interface UseInvestigateInTimelineValue {
+ onClick: (() => Promise) | undefined;
+}
+
+/**
+ * Custom hook that gets an {@link Indicator}, retrieves the field (from the RawIndicatorFieldId.Name)
+ * and value, then creates DataProviders used to do the Investigate in Timeline logic
+ * (see /kibana/x-pack/plugins/security_solution/public/threat_intelligence/use_investigate_in_timeline.ts)
+ */
+export const useInvestigateInTimeline = ({
+ indicator,
+}: UseInvestigateInTimelineParam): UseInvestigateInTimelineValue => {
+ const securitySolutionContext = useContext(SecuritySolutionContext);
+
+ const { key, value } = getIndicatorFieldAndValue(indicator, RawIndicatorFieldId.Name);
+
+ if (!value || value === EMPTY_VALUE || !key) {
+ return {} as unknown as UseInvestigateInTimelineValue;
+ }
+
+ const dataProviders: DataProvider[] = [...IndicatorFieldEventEnrichmentMap[key], key].map(
+ (e: string) => generateDataProvider(e, value)
+ );
+
+ const to = unwrapValue(indicator, RawIndicatorFieldId.TimeStamp) as string;
+ const from = moment(to).subtract(10, 'm').toISOString();
+
+ const investigateInTimelineClick = securitySolutionContext?.getUseInvestigateInTimeline({
+ dataProviders,
+ from,
+ to,
+ });
+
+ return {
+ onClick: investigateInTimelineClick,
+ };
+};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.test.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.test.ts
new file mode 100644
index 0000000000000..e88521fb2efdf
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.test.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { generateDataProvider } from './data-provider';
+
+describe('generateDataProvider', () => {
+ it('should return DataProvider object', () => {
+ const mockField: string = 'field';
+ const mockValue: string = 'value';
+
+ const dataProvider = generateDataProvider(mockField, mockValue);
+ expect(dataProvider.id).toContain(mockField);
+ expect(dataProvider.id).toContain(mockValue);
+ expect(dataProvider.name).toEqual(mockValue);
+ });
+});
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.ts
new file mode 100644
index 0000000000000..43a3ddc032541
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { DataProvider, QueryOperator } from '@kbn/timelines-plugin/common';
+
+/**
+ * Generate a DataProvider object to use when adding/investigating to/in a timeline.
+ * @param field used to generate the DataProvider id as well as its queryMatch
+ * @param value used to generate the DataProvider id as well as its name and queryMatch
+ */
+export const generateDataProvider = (field: string, value: string): DataProvider => {
+ const operator = ':' as QueryOperator;
+ const id: string = `timeline-indicator-${field}-${value}`;
+
+ return {
+ and: [],
+ enabled: true,
+ id,
+ name: value,
+ excluded: false,
+ kqlQuery: '',
+ queryMatch: {
+ field,
+ value,
+ operator,
+ },
+ };
+};
From 894e2400d07bd3dda7575256614c426ce4b47391 Mon Sep 17 00:00:00 2001
From: PhilippeOberti
Date: Thu, 15 Sep 2022 09:53:49 +0200
Subject: [PATCH 4/8] [TIP] Fix data-provider file should be snake_case
---
.../timeline/components/add_to_timeline/add_to_timeline.tsx | 2 +-
.../modules/timeline/hooks/use_investigate_in_timeline.ts | 2 +-
.../lib/{data-provider.test.ts => data_provider.test.ts} | 2 +-
.../modules/timeline/lib/{data-provider.ts => data_provider.ts} | 0
4 files changed, 3 insertions(+), 3 deletions(-)
rename x-pack/plugins/threat_intelligence/public/modules/timeline/lib/{data-provider.test.ts => data_provider.test.ts} (92%)
rename x-pack/plugins/threat_intelligence/public/modules/timeline/lib/{data-provider.ts => data_provider.ts} (100%)
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
index f303c5d783021..4ca644307eb55 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
@@ -10,7 +10,7 @@ import { DataProvider } from '@kbn/timelines-plugin/common';
import { AddToTimelineButtonProps } from '@kbn/timelines-plugin/public';
import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui/src/components/button';
import { EuiContextMenuItem } from '@elastic/eui';
-import { generateDataProvider } from '../../lib/data-provider';
+import { generateDataProvider } from '../../lib/data_provider';
import { ComponentType } from '../../../../../common/types/component_type';
import { getIndicatorFieldAndValue } from '../../../indicators/lib/field_value';
import { EMPTY_VALUE } from '../../../../../common/constants';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts
index 06f4adb933167..095960fbe484f 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts
@@ -8,7 +8,7 @@
import { useContext } from 'react';
import moment from 'moment';
import { DataProvider } from '@kbn/timelines-plugin/common';
-import { generateDataProvider } from '../lib/data-provider';
+import { generateDataProvider } from '../lib/data_provider';
import { SecuritySolutionContext } from '../../../containers/security_solution_context';
import { getIndicatorFieldAndValue } from '../../indicators/lib/field_value';
import { unwrapValue } from '../../indicators/lib/unwrap_value';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.test.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data_provider.test.ts
similarity index 92%
rename from x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.test.ts
rename to x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data_provider.test.ts
index e88521fb2efdf..33cd740a9ce29 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.test.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data_provider.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { generateDataProvider } from './data-provider';
+import { generateDataProvider } from './data_provider';
describe('generateDataProvider', () => {
it('should return DataProvider object', () => {
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data_provider.ts
similarity index 100%
rename from x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data-provider.ts
rename to x-pack/plugins/threat_intelligence/public/modules/timeline/lib/data_provider.ts
From 938597d279dcc6e330c5ff8f79e1525cf854f103 Mon Sep 17 00:00:00 2001
From: PhilippeOberti
Date: Thu, 15 Sep 2022 10:08:12 +0200
Subject: [PATCH 5/8] [TIP] Fix wrong namespace for translation id
---
.../public/threat_intelligence/translations.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/public/threat_intelligence/translations.ts b/x-pack/plugins/security_solution/public/threat_intelligence/translations.ts
index d795abda3e95e..02553c2c5140a 100644
--- a/x-pack/plugins/security_solution/public/threat_intelligence/translations.ts
+++ b/x-pack/plugins/security_solution/public/threat_intelligence/translations.ts
@@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n';
export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate(
- 'xpack.threatIntelligence.investigateInTimelineTitle',
+ 'xpack.securitySolution.threatIntelligence.investigateInTimelineTitle',
{
defaultMessage: 'Investigate in timeline',
}
From d621e208885dd789eeb9ef9684041d93cab351ea Mon Sep 17 00:00:00 2001
From: PhilippeOberti
Date: Thu, 15 Sep 2022 13:54:57 +0200
Subject: [PATCH 6/8] [TIP] Bump kbn-optimizer limit for threatIntelligence
ran command: node scripts/build_kibana_platform_plugins --update-limits
---
packages/kbn-optimizer/limits.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 511d230ec968d..7f16efddb5e05 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -108,7 +108,7 @@ pageLoadAssetSize:
synthetics: 40958
telemetry: 51957
telemetryManagementSection: 38586
- threatIntelligence: 29195
+ threatIntelligence: 44299
timelines: 327300
transform: 41007
triggersActionsUi: 119000
From eb5538ae6fa52e1acbe0ae4f6145d89c64383145 Mon Sep 17 00:00:00 2001
From: PhilippeOberti
Date: Mon, 19 Sep 2022 07:57:10 -0500
Subject: [PATCH 7/8] [TIP] Fix many small UI issues
- add EuiTooltip for all EuiButtonIcon
- add missing translations
- replace css with EuiFlexGroup where possible
- extract fieldValueValid logic
---
.../indicator_barchart_legend_action.tsx | 22 +++--
.../indicator_value_actions.tsx | 11 ++-
.../components/block/indicator_block.tsx | 2 +-
.../indicators_table/actions_row_cell.tsx | 8 +-
.../indicators_table/cell_actions.tsx | 6 +-
.../components/indicators_table/styles.ts | 18 ----
.../open_indicator_flyout_button.tsx | 18 ++--
.../indicators/lib/field_value.test.ts | 83 +++++++++++++-----
.../modules/indicators/lib/field_value.ts | 10 +++
.../__snapshots__/filter_in.test.tsx.snap | 84 +++++++++++--------
.../components/filter_in/filter_in.tsx | 37 ++++----
.../__snapshots__/filter_out.test.tsx.snap | 84 +++++++++++--------
.../components/filter_out/filter_out.tsx | 37 ++++----
.../add_to_timeline.test.tsx.snap | 68 +++++++++------
.../add_to_timeline/add_to_timeline.tsx | 26 ++++--
.../components/add_to_timeline/styles.ts | 5 --
.../investigate_in_timeline_button.tsx | 6 +-
...gate_in_timeline_button_icon.test.tsx.snap | 12 +--
.../investigate_in_timeline_button_icon.tsx | 23 +++--
.../styles.ts | 18 ----
.../hooks/use_investigate_in_timeline.ts | 8 +-
21 files changed, 329 insertions(+), 257 deletions(-)
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/styles.ts
delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/styles.ts
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_barchart_legend_action/indicator_barchart_legend_action.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_barchart_legend_action/indicator_barchart_legend_action.tsx
index e015e409ec227..c87f812da76fb 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_barchart_legend_action/indicator_barchart_legend_action.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_barchart_legend_action/indicator_barchart_legend_action.tsx
@@ -6,7 +6,8 @@
*/
import React, { useState, VFC } from 'react';
-import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
+import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover, EuiToolTip } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { ComponentType } from '../../../../../common/types/component_type';
import { FilterIn } from '../../../query_bar/components/filter_in';
import { FilterOut } from '../../../query_bar/components/filter_out';
@@ -17,6 +18,10 @@ export const TIMELINE_BUTTON_TEST_ID = 'tiBarchartTimelineButton';
export const FILTER_IN_BUTTON_TEST_ID = 'tiBarchartFilterInButton';
export const FILTER_OUT_BUTTON_TEST_ID = 'tiBarchartFilterOutButton';
+const BUTTON_LABEL = i18n.translate('xpack.threatIntelligence.indicator.barChart.popover', {
+ defaultMessage: 'More actions',
+});
+
export interface IndicatorBarchartLegendActionProps {
/**
* Indicator
@@ -59,12 +64,15 @@ export const IndicatorBarchartLegendAction: VFC setPopover(!isPopoverOpen)}
- />
+
+ setPopover(!isPopoverOpen)}
+ />
+
}
isOpen={isPopoverOpen}
closePopover={() => setPopover(false)}
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx
index 1fdc58f85cfcf..f3c5fd7c2e7d8 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx
@@ -7,12 +7,12 @@
import type { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
import React, { VFC } from 'react';
-import { EMPTY_VALUE } from '../../../../../common/constants';
+import { EuiFlexGroup } from '@elastic/eui';
import { Indicator } from '../../../../../common/types/indicator';
import { FilterIn } from '../../../query_bar/components/filter_in';
import { FilterOut } from '../../../query_bar/components/filter_out';
import { AddToTimeline } from '../../../timeline/components/add_to_timeline';
-import { getIndicatorFieldAndValue } from '../../lib/field_value';
+import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../lib/field_value';
export const TIMELINE_BUTTON_TEST_ID = 'TimelineButton';
export const FILTER_IN_BUTTON_TEST_ID = 'FilterInButton';
@@ -44,8 +44,7 @@ export const IndicatorValueActions: VFC = ({
...props
}) => {
const { key, value } = getIndicatorFieldAndValue(indicator, field);
-
- if (!key || value === EMPTY_VALUE || !key) {
+ if (!fieldAndValueValid(key, value)) {
return null;
}
@@ -54,7 +53,7 @@ export const IndicatorValueActions: VFC = ({
const timelineTestId = `${props['data-test-subj']}${TIMELINE_BUTTON_TEST_ID}`;
return (
- <>
+
= ({
field={field}
data-test-subj={timelineTestId}
/>
- >
+
);
};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.tsx
index 3fbd7d7365d50..9537131a574a3 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.tsx
@@ -25,7 +25,7 @@ const VisibleOnHover = euiStyled.div`
& .actionsWrapper {
visibility: hidden;
display: inline-block;
- margin-inline-start: ${theme.eui.euiSizeXS};
+ margin-inline-start: ${theme.eui.euiSizeS};
}
&:hover .actionsWrapper {
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx
index 277b64fcacc3d..1744bf8ac06ce 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx
@@ -6,17 +6,15 @@
*/
import React, { useContext, VFC } from 'react';
+import { EuiFlexGroup } from '@elastic/eui';
import { InvestigateInTimelineButtonIcon } from '../../../timeline/components/investigate_in_timeline_button_icon';
import { Indicator } from '../../../../../common/types/indicator';
import { OpenIndicatorFlyoutButton } from '../open_indicator_flyout_button/open_indicator_flyout_button';
import { IndicatorsTableContext } from './context';
-import { useStyles } from './styles';
const INVESTIGATE_TEST_ID = 'tiIndicatorTableInvestigateInTimelineButtonIcon';
export const ActionsRowCell: VFC<{ indicator: Indicator }> = ({ indicator }) => {
- const styles = useStyles();
-
const indicatorTableContext = useContext(IndicatorsTableContext);
if (!indicatorTableContext) {
@@ -26,13 +24,13 @@ export const ActionsRowCell: VFC<{ indicator: Indicator }> = ({ indicator }) =>
const { setExpanded, expanded } = indicatorTableContext;
return (
-
+
-
+
);
};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx
index fa255f053ab67..0f111f96c4c25 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx
@@ -8,11 +8,10 @@
import React, { VFC } from 'react';
import { EuiDataGridColumnCellActionProps } from '@elastic/eui/src/components/datagrid/data_grid_types';
import { ComponentType } from '../../../../../common/types/component_type';
-import { EMPTY_VALUE } from '../../../../../common/constants';
import { Indicator } from '../../../../../common/types/indicator';
import { Pagination } from '../../hooks/use_indicators';
import { AddToTimeline } from '../../../timeline/components/add_to_timeline';
-import { getIndicatorFieldAndValue } from '../../lib/field_value';
+import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../lib/field_value';
import { FilterIn } from '../../../query_bar/components/filter_in';
import { FilterOut } from '../../../query_bar/components/filter_out';
@@ -47,8 +46,7 @@ export const CellActions: VFC = ({
}) => {
const indicator = indicators[rowIndex % pagination.pageSize];
const { key, value } = getIndicatorFieldAndValue(indicator, columnId);
-
- if (!value || value === EMPTY_VALUE || !key) {
+ if (!fieldAndValueValid(key, value)) {
return <>>;
}
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/styles.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/styles.ts
deleted file mode 100644
index 3ae7bf4ef16d9..0000000000000
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/styles.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { CSSObject } from '@emotion/react';
-
-export const useStyles = () => {
- const rowActionsDiv: CSSObject = {
- display: 'flex',
- };
-
- return {
- rowActionsDiv,
- };
-};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.tsx
index 48900019265c7..08a27381ce9a7 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.tsx
@@ -12,6 +12,13 @@ import { Indicator } from '../../../../../common/types/indicator';
export const BUTTON_TEST_ID = 'tiToggleIndicatorFlyoutButton';
+const BUTTON_LABEL: string = i18n.translate(
+ 'xpack.threatIntelligence.indicator.table.viewDetailsButton',
+ {
+ defaultMessage: 'View details',
+ }
+);
+
export interface OpenIndicatorFlyoutButtonProps {
/**
* {@link Indicator} passed to the flyout component.
@@ -35,22 +42,15 @@ export const OpenIndicatorFlyoutButton: VFC = ({
onOpen,
isOpen,
}) => {
- const buttonLabel: string = i18n.translate(
- 'xpack.threatIntelligence.indicator.table.viewDetailsButton',
- {
- defaultMessage: 'View details',
- }
- );
-
return (
-
+
onOpen(indicator)}
/>
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.test.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.test.ts
index fa9746e80cc21..bd6a36aaf2d50 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.test.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.test.ts
@@ -5,39 +5,76 @@
* 2.0.
*/
-import { getIndicatorFieldAndValue } from './field_value';
+import { fieldAndValueValid, getIndicatorFieldAndValue } from './field_value';
import {
generateMockFileIndicator,
generateMockUrlIndicator,
} from '../../../../common/types/indicator';
+import { EMPTY_VALUE } from '../../../../common/constants';
-describe('getIndicatorFieldAndValue()', () => {
- it('should return field/value pair for an indicator', () => {
- const mockData = generateMockUrlIndicator();
- const mockKey = 'threat.feed.name';
+describe('field_value', () => {
+ describe('getIndicatorFieldAndValue()', () => {
+ it('should return field/value pair for an indicator', () => {
+ const mockData = generateMockUrlIndicator();
+ const mockKey = 'threat.feed.name';
- const result = getIndicatorFieldAndValue(mockData, mockKey);
- expect(result.key).toEqual(mockKey);
- expect(result.value).toEqual((mockData.fields[mockKey] as unknown as string[])[0]);
- });
+ const result = getIndicatorFieldAndValue(mockData, mockKey);
+ expect(result.key).toEqual(mockKey);
+ expect(result.value).toEqual((mockData.fields[mockKey] as unknown as string[])[0]);
+ });
+
+ it('should return a null value for an incorrect field', () => {
+ const mockData = generateMockUrlIndicator();
+ const mockKey = 'abc';
+
+ const result = getIndicatorFieldAndValue(mockData, mockKey);
+ expect(result.key).toEqual(mockKey);
+ expect(result.value).toBeNull();
+ });
- it('should return a null value for an incorrect field', () => {
- const mockData = generateMockUrlIndicator();
- const mockKey = 'abc';
+ it('should return field/value pair for an indicator and DisplayName field', () => {
+ const mockData = generateMockFileIndicator();
+ const mockKey = 'threat.indicator.name';
- const result = getIndicatorFieldAndValue(mockData, mockKey);
- expect(result.key).toEqual(mockKey);
- expect(result.value).toBeNull();
+ const result = getIndicatorFieldAndValue(mockData, mockKey);
+ expect(result.key).toEqual(
+ (mockData.fields['threat.indicator.name_origin'] as unknown as string[])[0]
+ );
+ expect(result.value).toEqual((mockData.fields[mockKey] as unknown as string[])[0]);
+ });
});
- it('should return field/value pair for an indicator and DisplayName field', () => {
- const mockData = generateMockFileIndicator();
- const mockKey = 'threat.indicator.name';
+ describe('fieldAndValueValid()', () => {
+ it('should return false for null value', () => {
+ const mockField = 'abc';
+ const mockValue = null;
+
+ const result = fieldAndValueValid(mockField, mockValue);
+ expect(result).toEqual(false);
+ });
+
+ it(`should return false for ${EMPTY_VALUE} value`, () => {
+ const mockField = 'abc';
+ const mockValue = EMPTY_VALUE;
+
+ const result = fieldAndValueValid(mockField, mockValue);
+ expect(result).toEqual(false);
+ });
+
+ it('should return false for empty field', () => {
+ const mockField = '';
+ const mockValue = 'abc';
+
+ const result = fieldAndValueValid(mockField, mockValue);
+ expect(result).toEqual(false);
+ });
+
+ it('should return true if field and value are correct', () => {
+ const mockField = 'abc';
+ const mockValue = 'abc';
- const result = getIndicatorFieldAndValue(mockData, mockKey);
- expect(result.key).toEqual(
- (mockData.fields['threat.indicator.name_origin'] as unknown as string[])[0]
- );
- expect(result.value).toEqual((mockData.fields[mockKey] as unknown as string[])[0]);
+ const result = fieldAndValueValid(mockField, mockValue);
+ expect(result).toEqual(true);
+ });
});
});
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.ts
index f463537b120c4..958b33e6a6347 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/field_value.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { EMPTY_VALUE } from '../../../../common/constants';
import { unwrapValue } from './unwrap_value';
import { Indicator, RawIndicatorFieldId } from '../../../../common/types/indicator';
@@ -29,3 +30,12 @@ export const getIndicatorFieldAndValue = (
value,
};
};
+
+/**
+ * Checks if field and value are correct
+ * @param field Indicator string field
+ * @param value Indicator string|null value for the field
+ * @returns true if correct, false if not
+ */
+export const fieldAndValueValid = (field: string | null, value: string | null): boolean =>
+ value != null && value !== '' && value !== EMPTY_VALUE && field != null && field !== '';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/__snapshots__/filter_in.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/__snapshots__/filter_in.test.tsx.snap
index 1ceba15da5ad6..34e37aaf1dd40 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/__snapshots__/filter_in.test.tsx.snap
+++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/__snapshots__/filter_in.test.tsx.snap
@@ -127,6 +127,31 @@ Object {
"asFragment": [Function],
"baseElement":
+ ,
+ "container":
+
@@ -142,24 +167,7 @@ Object {
/>
-
-
+
+
+
+
+