Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TIP] Add to timeline #138836

Merged
merged 5 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions x-pack/plugins/threat_intelligence/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Verify your node version [here](https://github.com/elastic/kibana/blob/main/.nod

**Run Kibana:**

> **Important:**
>
> See here to get your `kibana.yaml` to enable the Threat Intelligence plugin.

```
yarn kbn reset && yarn kbn bootstrap
yarn start --no-base-path
Expand Down
15 changes: 15 additions & 0 deletions x-pack/plugins/threat_intelligence/common/types/indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* 2.0.
*/

/**
* Enum of indicator fields supported by the Threat Intelligence plugin.
*/
export enum RawIndicatorFieldId {
Type = 'threat.indicator.type',
FirstSeen = 'threat.indicator.first_seen',
Expand All @@ -21,11 +24,17 @@ export enum RawIndicatorFieldId {
TimeStamp = '@timestamp',
}

/**
* Threat Intelligence Indicator interface.
*/
export interface Indicator {
_id?: unknown;
fields: Partial<Record<RawIndicatorFieldId, unknown[]>>;
}

/**
* Used to create new Indicators, used mainly in jest unit tests and Storybook stories.
*/
export const generateMockIndicator = (): Indicator => ({
fields: {
'@timestamp': ['2022-01-01T01:01:01.000Z'],
Expand All @@ -37,6 +46,9 @@ export const generateMockIndicator = (): Indicator => ({
_id: Math.random(),
});

/**
* Used to create new url-type Indicators, used mainly in jest unit tests and Storybook stories.
*/
export const generateMockUrlIndicator = (): Indicator => {
const indicator = generateMockIndicator();

Expand All @@ -46,6 +58,9 @@ export const generateMockUrlIndicator = (): Indicator => {
return indicator;
};

/**
* Used to create new file-type Indicators, used mainly in jest unit tests and Storybook stories.
*/
export const generateMockFileIndicator = (): Indicator => {
const indicator = generateMockIndicator();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
* 2.0.
*/

import { login } from '../../tasks/login';
import { login } from '../tasks/login';
import {
EMPTY_PAGE_BODY,
EMPTY_PAGE_DOCS_LINK,
EMPTY_PAGE_INTEGRATIONS_LINK,
} from '../../screens/empty_page';
} from '../screens/empty_page';

const THREAT_INTEL_PATH = '/app/security/threat_intelligence/';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import {
ENDING_BREADCRUMB,
FIELD_BROWSER,
FIELD_BROWSER_MODAL,
} from '../../screens/indicators';
import { login } from '../../tasks/login';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import { selectRange } from '../../tasks/select_range';
} from '../screens/indicators';
import { login } from '../tasks/login';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { selectRange } from '../tasks/select_range';

before(() => {
login();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 {
BARCHART_TIMELINE_BUTTON,
FLYOUT_CLOSE_BUTTON,
FLYOUT_TABLE_ROW_TIMELINE_BUTTON,
INDICATOR_TYPE_CELL,
INDICATORS_TABLE_CELL_TIMELINE_BUTTON,
TIMELINE_DRAGGABLE_ITEM,
TOGGLE_FLYOUT_BUTTON,
UNTITLED_TIMELINE_BUTTON,
} from '../screens/indicators';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { login } from '../tasks/login';
import { selectRange } from '../tasks/select_range';

const THREAT_INTELLIGENCE = '/app/security/threat_intelligence/indicators';

before(() => {
login();
});

describe('Indicators', () => {
before(() => {
esArchiverLoad('threat_intelligence');
});
after(() => {
esArchiverUnload('threat_intelligence');
});

describe('Indicators timeline interactions', () => {
before(() => {
cy.visit(THREAT_INTELLIGENCE);

selectRange();
});

it('should add entry in timeline when clicking in the barchart legend', () => {
cy.get(BARCHART_TIMELINE_BUTTON).should('exist').first().click();
cy.get(UNTITLED_TIMELINE_BUTTON).should('exist').first().click();
cy.get(TIMELINE_DRAGGABLE_ITEM).should('exist');
});

it('should add entry in timeline when clicking in an indicator table cell', () => {
cy.get(INDICATOR_TYPE_CELL).first().trigger('mouseover');
cy.get(INDICATORS_TABLE_CELL_TIMELINE_BUTTON).should('exist').first().click();
cy.get(UNTITLED_TIMELINE_BUTTON).should('exist').first().click();
cy.get(TIMELINE_DRAGGABLE_ITEM).should('exist');
});

it('should add entry in timeline when clicking in an indicators flyout row', () => {
cy.get(TOGGLE_FLYOUT_BUTTON).first().click({ force: true });
cy.get(FLYOUT_TABLE_ROW_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');
});
});
});
13 changes: 13 additions & 0 deletions x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export const INDICATORS_TABLE = `[data-test-subj="tiIndicatorsTable"]`;

export const TOGGLE_FLYOUT_BUTTON = `[data-test-subj="tiToggleIndicatorFlyoutButton"]`;

export const FLYOUT_CLOSE_BUTTON = `[data-test-subj="euiFlyoutCloseButton"]`;

export const FLYOUT_TITLE = `[data-test-subj="tiIndicatorFlyoutTitle"]`;

export const FLYOUT_TABS = `[data-test-subj="tiIndicatorFlyoutTabs"]`;
Expand Down Expand Up @@ -46,3 +48,14 @@ export const FIELD_BROWSER_MODAL = `[data-test-subj="fields-browser-container"]`
export const FIELD_BROWSER_MODAL_SOURCE_CHECKBOX = `[data-test-subj="field-_source-checkbox"]`;

export const FIELD_BROWSER_CLOSE = `[data-test-subj="close"]`;

export const BARCHART_TIMELINE_BUTTON = '[data-test-subj="tiTimelineButton"]';

export const INDICATORS_TABLE_CELL_TIMELINE_BUTTON =
'[data-test-subj="tiIndicatorsTableCellTimelineButton"]';

export const FLYOUT_TABLE_ROW_TIMELINE_BUTTON = '[data-test-subj="tiFlyoutTableRowTimelineButton"]';

export const UNTITLED_TIMELINE_BUTTON = '[data-test-subj="flyoutOverlay"]';

export const TIMELINE_DRAGGABLE_ITEM = '[data-test-subj="providerContainer"]';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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.
*/

/**
* Mock to map an indicator field to its type.
*/
export const generateFieldTypeMap = (): { [id: string]: string } => ({
'@timestamp': 'date',
'threat.indicator.ip': 'ip',
'threat.indicator.first_seen': 'date',
'threat.feed.name': 'string',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { HoverActionsConfig } from '@kbn/timelines-plugin/public/components/hover_actions';
import { EuiButtonIcon } from '@elastic/eui';
import { TimelinesUIStart } from '@kbn/timelines-plugin/public';

/**
* Returns a default object to mock the timelines plugin for our unit tests and storybook stories.
* The button mocks a window.alert onClick event.
*/
export const mockKibanaTimelinesService: TimelinesUIStart = {
getHoverActions(): HoverActionsConfig {
return {
getAddToTimelineButton: () => (
<EuiButtonIcon
iconType="timeline"
iconSize="s"
onClick={() => window.alert('Add to Timeline button clicked')}
/>
),
} as unknown as HoverActionsConfig;
},
} as unknown as TimelinesUIStart;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 { EuiText } from '@elastic/eui';
import { TriggersAndActionsUIPublicPluginStart as TriggersActionsStart } from '@kbn/triggers-actions-ui-plugin/public';

/**
* Returns a default object to mock the triggers actions ui plugin for our unit tests and storybook stories.
*/
export const mockTriggersActionsUiService: TriggersActionsStart = {
getFieldBrowser: () => (
<EuiText style={{ display: 'inline' }} size="xs">
Fields
</EuiText>
),
} as unknown as TriggersActionsStart;
Original file line number Diff line number Diff line change
@@ -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 { IUiSettingsClient } from '@kbn/core/public';
import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../common/constants';

/**
* Creates an object to pass to the uiSettings property when creating a KibanaReacrContext (see src/plugins/kibana_react/public/context/context.tsx).
* @param dateFormat defaults to ''
* @param timezone defaults to 'UTC
* @returns the object {@link IUiSettingsClient}
*/
export const mockUiSettingsService = (dateFormat: string = '', timezone: string = 'UTC') =>
({
get: (key: string) => {
const settings = {
[DEFAULT_DATE_FORMAT]: dateFormat,
[DEFAULT_DATE_FORMAT_TZ]: timezone,
};
// @ts-expect-error
return settings[key];
},
} as unknown as IUiSettingsClient);

/**
* Mocks date format or timezone for testing.
* @param key dateFormat | dateFormat:tz
* @returns string
*/
export function mockUiSetting(key: string): string | undefined {
if (key === 'dateFormat') {
return 'MMM D, YYYY @ HH:mm:ss.SSS';
}
if (key === 'dateFormat:tz') {
return 'America/New_York';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import React, { ReactNode, VFC } from 'react';
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { CoreStart, IUiSettingsClient } from '@kbn/core/public';
import { IUiSettingsClient } from '@kbn/core/public';
import { TimelinesUIStart } from '@kbn/timelines-plugin/public';
import { SecuritySolutionContext } from '../../containers/security_solution_context';
import { getSecuritySolutionContextMock } from './mock_security_context';

Expand All @@ -21,6 +22,10 @@ export interface KibanaContextMock {
* For the core ui-settings package (see {@link IUiSettingsClient})
*/
uiSettings?: IUiSettingsClient;
/**
* For the timelines plugin
*/
timelines: TimelinesUIStart;
}

export interface StoryProvidersComponentProps {
Expand All @@ -42,7 +47,7 @@ export const StoryProvidersComponent: VFC<StoryProvidersComponentProps> = ({
children,
kibana,
}) => {
const KibanaReactContext = createKibanaReactContext(kibana as CoreStart);
const KibanaReactContext = createKibanaReactContext(kibana);
const securitySolutionContextMock = getSecuritySolutionContextMock();

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@

import moment from 'moment/moment';
import React, { FC } from 'react';
import { BehaviorSubject } from 'rxjs';
import { I18nProvider } from '@kbn/i18n-react';
import { coreMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import type { IStorage } from '@kbn/kibana-utils-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
import { BehaviorSubject } from 'rxjs';
import { getSecuritySolutionContextMock } from './mock_security_context';
import { mockUiSetting } from './mock_kibana_ui_setting';
import { createTGridMocks } from '@kbn/timelines-plugin/public/mock';
import { KibanaContext } from '../../hooks/use_kibana';
import { SecuritySolutionPluginContext } from '../../types';
import { getSecuritySolutionContextMock } from './mock_security_context';
import { mockUiSetting } from './mock_kibana_ui_settings_service';
import { SecuritySolutionContext } from '../../containers/security_solution_context';

export const localStorageMock = (): IStorage => {
Expand Down Expand Up @@ -90,6 +91,8 @@ const dataServiceMock = {
},
};

const timelinesServiceMock = createTGridMocks();

const core = coreMock.createStart();
const coreServiceMock = {
...core,
Expand All @@ -106,6 +109,7 @@ export const mockedServices = {
triggersActionsUi: {
getFieldBrowser: jest.fn().mockReturnValue(null),
},
timelines: timelinesServiceMock,
};

export const TestProvidersComponent: FC = ({ children }) => (
Expand Down
Loading