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 { />
-
- , - "container":
-
- -
+
, "debug": [Function], "findAllByAltText": [Function], @@ -220,6 +228,29 @@ Object { "asFragment": [Function], "baseElement":
+ + + +
+ , + "container":
+ -
- , - "container":
- +
, "debug": [Function], "findAllByAltText": [Function], diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.tsx index 44893c02c685e..d801f46915921 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.tsx @@ -7,13 +7,12 @@ import React, { useCallback, VFC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonEmpty, EuiButtonIcon, EuiContextMenuItem } from '@elastic/eui'; +import { EuiButtonEmpty, EuiButtonIcon, EuiContextMenuItem, EuiToolTip } from '@elastic/eui'; import { Filter } from '@kbn/es-query'; import { ComponentType } from '../../../../../common/types/component_type'; import { useIndicatorsFiltersContext } from '../../../indicators/hooks/use_indicators_filters_context'; -import { getIndicatorFieldAndValue } from '../../../indicators/lib/field_value'; +import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../indicators/lib/field_value'; import { FilterIn as FilterInConst, updateFiltersArray } from '../../lib/filter'; -import { EMPTY_VALUE } from '../../../../../common/constants'; import { Indicator } from '../../../../../common/types/indicator'; import { useStyles } from './styles'; @@ -66,16 +65,18 @@ export const FilterIn: VFC = ({ data, field, type, as: Component, filterManager.setFilters(newFilters); }, [filterManager, key, value]); - if (!value || value === EMPTY_VALUE || !key) { + if (!fieldAndValueValid(key, value)) { return <>; } if (type === ComponentType.EuiDataGrid) { return ( -
- {/* @ts-ignore*/} - -
+ +
+ {/* @ts-ignore*/} + +
+
); } @@ -94,14 +95,16 @@ export const FilterIn: VFC = ({ data, field, type, as: Component, } return ( - + + + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/__snapshots__/filter_out.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/__snapshots__/filter_out.test.tsx.snap index 5ace1d59f4bd8..bfee4c6363ba7 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/__snapshots__/filter_out.test.tsx.snap +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/__snapshots__/filter_out.test.tsx.snap @@ -127,6 +127,31 @@ Object { "asFragment": [Function], "baseElement":
+ +
+ +
+
+
+ , + "container":
+
@@ -142,24 +167,7 @@ Object { />
-
- , - "container":
-
- -
+
, "debug": [Function], "findAllByAltText": [Function], @@ -220,6 +228,29 @@ Object { "asFragment": [Function], "baseElement":
+ + + +
+ , + "container":
+ -
- , - "container":
- +
, "debug": [Function], "findAllByAltText": [Function], diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.tsx index dd7a644d43fe2..67f28a66b486e 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.tsx @@ -7,13 +7,12 @@ import React, { useCallback, VFC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonEmpty, EuiButtonIcon, EuiContextMenuItem } from '@elastic/eui'; +import { EuiButtonEmpty, EuiButtonIcon, EuiContextMenuItem, EuiToolTip } from '@elastic/eui'; import { Filter } from '@kbn/es-query'; import { ComponentType } from '../../../../../common/types/component_type'; import { useIndicatorsFiltersContext } from '../../../indicators/hooks/use_indicators_filters_context'; -import { getIndicatorFieldAndValue } from '../../../indicators/lib/field_value'; +import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../indicators/lib/field_value'; import { FilterOut as FilterOutConst, updateFiltersArray } from '../../lib/filter'; -import { EMPTY_VALUE } from '../../../../../common/constants'; import { Indicator } from '../../../../../common/types/indicator'; import { useStyles } from './styles'; @@ -66,16 +65,18 @@ export const FilterOut: VFC = ({ data, field, type, as: Componen filterManager.setFilters(newFilters); }, [filterManager, key, value]); - if (!value || value === EMPTY_VALUE || !key) { + if (!fieldAndValueValid(key, value)) { return <>; } if (type === ComponentType.EuiDataGrid) { return ( -
- {/* @ts-ignore*/} - -
+ +
+ {/* @ts-ignore*/} + +
+
); } @@ -94,14 +95,16 @@ export const FilterOut: VFC = ({ data, field, type, as: Componen } return ( - + + + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/__snapshots__/add_to_timeline.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/__snapshots__/add_to_timeline.test.tsx.snap index c56f657d1d905..c7373eb2f1645 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/__snapshots__/add_to_timeline.test.tsx.snap +++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/__snapshots__/add_to_timeline.test.tsx.snap @@ -249,8 +249,27 @@ Object { "asFragment": [Function], "baseElement":
+ +
+ + Add To Timeline + +
+
+
+ , + "container":
+
-
- , - "container":
-
- - Add To Timeline - -
+
, "debug": [Function], "findAllByAltText": [Function], @@ -330,8 +338,27 @@ Object { "asFragment": [Function], "baseElement":
+ +
+ + Add To Timeline + +
+
+
+ , + "container":
+
-
- , - "container":
-
- - Add To Timeline - -
+
, "debug": [Function], "findAllByAltText": [Function], 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 4ca644307eb55..2c0d2ec6c5f37 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 @@ -9,11 +9,12 @@ import React, { useRef, VFC } from 'react'; 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 { EuiContextMenuItem, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; 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'; +import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../indicators/lib/field_value'; import { useKibana } from '../../../../hooks/use_kibana'; import { Indicator } from '../../../../../common/types/indicator'; import { useStyles } from './styles'; @@ -62,11 +63,11 @@ export const AddToTimeline: VFC = ({ data, field, type, as, const { key, value } = typeof data === 'string' ? { key: field, value: data } : getIndicatorFieldAndValue(data, field); - if (!value || value === EMPTY_VALUE || !key) { + if (!fieldAndValueValid(key, value)) { return <>; } - const dataProvider: DataProvider[] = [generateDataProvider(key, value)]; + const dataProvider: DataProvider[] = [generateDataProvider(key, value as string)]; const addToTimelineProps: AddToTimelineButtonProps = { dataProvider, @@ -90,7 +91,10 @@ export const AddToTimeline: VFC = ({ data, field, type, as, onClick={() => contextMenuRef.current?.click()} {...props} > - Add to Timeline + ); @@ -99,8 +103,12 @@ export const AddToTimeline: VFC = ({ data, field, type, as, if (as) addToTimelineProps.Component = as; return ( -
- {addToTimelineButton(addToTimelineProps)} -
+ + {addToTimelineButton(addToTimelineProps)} + ); }; 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 d4f13c215a7a7..8faebdf600953 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,16 +8,11 @@ import { CSSObject } from '@emotion/react'; export const useStyles = () => { - const inlineFlex: CSSObject = { - display: 'inline-flex', - }; - const displayNone: CSSObject = { display: 'none', }; return { - inlineFlex, displayNone, }; }; 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 index 4791b507700f8..479f175275625 100644 --- 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 @@ -7,6 +7,7 @@ import React, { VFC } from 'react'; import { EuiButton } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useInvestigateInTimeline } from '../../hooks/use_investigate_in_timeline'; import { Indicator } from '../../../../../common/types/indicator'; @@ -40,7 +41,10 @@ export const InvestigateInTimelineButton: VFC 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 index a17cb9d47bb57..263e893f3f8b6 100644 --- 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 @@ -5,8 +5,8 @@ Object { "asFragment": [Function], "baseElement":
-
-
+
, "container":
-
-
+
, "debug": [Function], "findAllByAltText": [Function], 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 index 3585d0d2e62e6..888f420a3cac7 100644 --- 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 @@ -6,11 +6,17 @@ */ import React, { VFC } from 'react'; -import { EuiButtonIcon } from '@elastic/eui'; +import { EuiButtonIcon, EuiToolTip } 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'; + +const BUTTON_LABEL: string = i18n.translate( + 'xpack.threatIntelligence.investigateInTimelineButtonIcon', + { + defaultMessage: 'Investigate in Timeline', + } +); export interface InvestigateInTimelineButtonIconProps { /** @@ -34,8 +40,6 @@ export const InvestigateInTimelineButtonIcon: VFC { - const styles = useStyles(); - const { onClick } = useInvestigateInTimeline({ indicator: data }); if (!onClick) { @@ -43,14 +47,9 @@ export const InvestigateInTimelineButtonIcon: VFC + -
+ ); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/styles.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/styles.ts deleted file mode 100644 index cc1b166edbc76..0000000000000 --- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline_button_icon/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 inlineFlex: CSSObject = { - display: 'inline-flex', - }; - - return { - inlineFlex, - }; -}; 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 095960fbe484f..904a0afc4fd93 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 @@ -10,9 +10,8 @@ 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 { fieldAndValueValid, getIndicatorFieldAndValue } from '../../indicators/lib/field_value'; import { unwrapValue } from '../../indicators/lib/unwrap_value'; -import { EMPTY_VALUE } from '../../../../common/constants'; import { Indicator, IndicatorFieldEventEnrichmentMap, @@ -41,13 +40,12 @@ export const useInvestigateInTimeline = ({ const securitySolutionContext = useContext(SecuritySolutionContext); const { key, value } = getIndicatorFieldAndValue(indicator, RawIndicatorFieldId.Name); - - if (!value || value === EMPTY_VALUE || !key) { + if (!fieldAndValueValid(key, value)) { return {} as unknown as UseInvestigateInTimelineValue; } const dataProviders: DataProvider[] = [...IndicatorFieldEventEnrichmentMap[key], key].map( - (e: string) => generateDataProvider(e, value) + (e: string) => generateDataProvider(e, value as string) ); const to = unwrapValue(indicator, RawIndicatorFieldId.TimeStamp) as string; From 7d8135049e445ed5b3d0a832469320af1bdb3cc9 Mon Sep 17 00:00:00 2001 From: PhilippeOberti Date: Mon, 19 Sep 2022 11:46:16 -0500 Subject: [PATCH 8/8] [TIP] Code clean --- .../public/modules/indicators/lib/field_value.test.ts | 8 ++++++++ .../public/modules/indicators/lib/field_value.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) 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 bd6a36aaf2d50..22e5dafc7394e 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 @@ -53,6 +53,14 @@ describe('field_value', () => { expect(result).toEqual(false); }); + it('should return false for empty string value', () => { + const mockField = 'abc'; + const mockValue = ''; + + const result = fieldAndValueValid(mockField, mockValue); + expect(result).toEqual(false); + }); + it(`should return false for ${EMPTY_VALUE} value`, () => { const mockField = 'abc'; const mockValue = EMPTY_VALUE; 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 958b33e6a6347..6bdfc415fe01e 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 @@ -38,4 +38,4 @@ export const getIndicatorFieldAndValue = ( * @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 !== ''; + !!value && value !== EMPTY_VALUE && !!field;