From 167526da0de3abc61c0347d42a82c6bfbbe760fd Mon Sep 17 00:00:00 2001 From: Luke Gmys Date: Tue, 13 Sep 2022 13:17:58 +0200 Subject: [PATCH 01/28] [TIP] Add Indicator Overview tab (#140073) * [TIP] Add Indicator Overview tab * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../common/types/indicator.ts | 2 + .../cypress/integration/indicators.spec.ts | 10 +- .../cypress/tasks/login.ts | 12 +- .../cypress/tasks/navigation.ts | 20 --- .../cypress/tasks/select_range.ts | 4 +- .../common/mocks/mock_field_type_map.ts | 4 +- .../public/common/mocks/story_providers.tsx | 49 +++++-- .../public/common/mocks/test_providers.tsx | 25 ++-- .../components/layout/layout.stories.tsx | 15 ++- .../containers/field_types_provider.tsx | 33 +++++ .../public/hooks/use_field_types.ts | 13 ++ .../modules/empty_page/empty_page.stories.tsx | 12 +- .../indicator_field.stories.tsx | 53 -------- .../indicator_field_label/index.tsx | 8 ++ .../indicator_field_label.tsx | 71 +++++++++++ .../indicator_field.test.tsx.snap | 0 .../indicator_field_value/index.tsx | 8 ++ .../indicator_field.stories.tsx | 40 ++++++ .../indicator_field.test.tsx | 26 +--- .../indicator_field_value.tsx} | 12 +- .../indicator_value_actions/index.tsx | 8 ++ .../indicator_value_actions.tsx | 41 ++++++ .../indicator_empty_prompt/index.tsx | 8 ++ .../indicator_empty_prompt.stories.tsx | 24 ++++ .../indicator_empty_prompt.tsx | 37 ++++++ .../indicator_fields_table/index.tsx | 8 ++ .../indicator_fields_table.stories.tsx | 34 +++++ .../indicator_fields_table.tsx | 63 +++++++++ .../indicators_flyout.stories.tsx | 4 - .../indicators_flyout.test.tsx | 85 ++++++------- .../indicators_flyout/indicators_flyout.tsx | 45 ++++--- .../tabs}/indicators_flyout_json/index.tsx | 0 .../indicators_flyout_json.stories.tsx | 2 +- .../indicators_flyout_json.test.tsx | 11 +- .../indicators_flyout_json.tsx | 29 +---- .../components/block}/index.tsx | 2 +- .../block/indicator_block.stories.tsx | 31 +++++ .../components/block/indicator_block.tsx | 69 ++++++++++ .../highlighted_values_table.tsx | 54 ++++++++ .../highlighted_values_table/index.tsx | 8 ++ .../tabs/indicators_flyout_overview/index.tsx | 8 ++ .../indicators_flyout_overview.stories.tsx | 36 ++++++ .../indicators_flyout_overview.test.tsx | 48 +++++++ .../indicators_flyout_overview.tsx | 111 ++++++++++++++++ .../tabs}/indicators_flyout_table/index.tsx | 0 .../indicators_flyout_table.stories.tsx | 18 +-- .../indicators_flyout_table.test.tsx | 19 +-- .../indicators_flyout_table.tsx | 47 +++++++ .../indicators_flyout_table.tsx | 120 ------------------ .../indicators_table/cell_actions.tsx | 4 +- .../indicators_table/cell_renderer.tsx | 6 +- .../components/indicators_table/context.ts | 1 - .../hooks/use_column_settings.ts | 52 ++------ .../indicators_table.stories.tsx | 46 +++---- .../indicators_table/indicators_table.tsx | 22 +--- .../hooks/use_aggregated_indicators.ts | 10 +- .../modules/indicators/indicators_page.tsx | 57 +++++---- .../filter_in_out/filter_in_out.test.tsx | 2 +- .../filter_in_out/filter_in_out.tsx | 4 +- .../add_to_timeline/add_to_timeline.tsx | 6 +- .../scripts/generate_indicators.js | 4 +- .../translations/translations/fr-FR.json | 17 +-- .../translations/translations/ja-JP.json | 17 +-- .../translations/translations/zh-CN.json | 17 +-- 64 files changed, 1102 insertions(+), 550 deletions(-) delete mode 100644 x-pack/plugins/threat_intelligence/cypress/tasks/navigation.ts create mode 100644 x-pack/plugins/threat_intelligence/public/containers/field_types_provider.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/hooks/use_field_types.ts delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/indicator_field.stories.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/index.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/indicator_field_label.tsx rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field => indicator_field_value}/__snapshots__/indicator_field.test.tsx.snap (100%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/index.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.stories.tsx rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field => indicator_field_value}/indicator_field.test.tsx (63%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field/indicator_field.tsx => indicator_field_value/indicator_field_value.tsx} (80%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/index.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/index.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.stories.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/index.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.stories.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.tsx rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{ => indicators_flyout/tabs}/indicators_flyout_json/index.tsx (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{ => indicators_flyout/tabs}/indicators_flyout_json/indicators_flyout_json.stories.tsx (96%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{ => indicators_flyout/tabs}/indicators_flyout_json/indicators_flyout_json.test.tsx (82%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{ => indicators_flyout/tabs}/indicators_flyout_json/indicators_flyout_json.tsx (52%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field => indicators_flyout/tabs/indicators_flyout_overview/components/block}/index.tsx (87%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.stories.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/index.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/index.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.stories.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.test.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{ => indicators_flyout/tabs}/indicators_flyout_table/index.tsx (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{ => indicators_flyout/tabs}/indicators_flyout_table/indicators_flyout_table.stories.tsx (62%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{ => indicators_flyout/tabs}/indicators_flyout_table/indicators_flyout_table.test.tsx (72%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.tsx delete mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/indicators_flyout_table.tsx diff --git a/x-pack/plugins/threat_intelligence/common/types/indicator.ts b/x-pack/plugins/threat_intelligence/common/types/indicator.ts index 2edcbb5a829ea..69e76e18545c1 100644 --- a/x-pack/plugins/threat_intelligence/common/types/indicator.ts +++ b/x-pack/plugins/threat_intelligence/common/types/indicator.ts @@ -10,6 +10,7 @@ */ export enum RawIndicatorFieldId { Type = 'threat.indicator.type', + Confidence = 'threat.indicator.confidence', FirstSeen = 'threat.indicator.first_seen', LastSeen = 'threat.indicator.last_seen', MarkingTLP = 'threat.indicator.marking.tlp', @@ -44,6 +45,7 @@ export enum RawIndicatorFieldId { TimeStamp = '@timestamp', Id = '_id', Name = 'threat.indicator.name', + Description = 'threat.indicator.description', NameOrigin = 'threat.indicator.name_origin', } diff --git a/x-pack/plugins/threat_intelligence/cypress/integration/indicators.spec.ts b/x-pack/plugins/threat_intelligence/cypress/integration/indicators.spec.ts index d331f2937e41d..ccd91253012b3 100644 --- a/x-pack/plugins/threat_intelligence/cypress/integration/indicators.spec.ts +++ b/x-pack/plugins/threat_intelligence/cypress/integration/indicators.spec.ts @@ -78,12 +78,16 @@ describe('Indicators', () => { cy.get(TOGGLE_FLYOUT_BUTTON).first().click({ force: true }); - cy.get(FLYOUT_TITLE).should('contain', 'Indicator:'); + cy.get(FLYOUT_TITLE).should('contain', 'Indicator details'); - cy.get(FLYOUT_TABLE).should('exist').and('contain.text', 'threat.indicator.type'); + cy.get(FLYOUT_TABS).should('exist').children().should('have.length', 3); + + cy.get(FLYOUT_TABS).should('exist'); + cy.get(`${FLYOUT_TABS} button:nth-child(2)`).click(); - cy.get(FLYOUT_TABS).should('exist').children().should('have.length', 2).last().click(); + cy.get(FLYOUT_TABLE).should('exist').and('contain.text', 'threat.indicator.type'); + cy.get(`${FLYOUT_TABS} button:nth-child(3)`).click(); cy.get(FLYOUT_JSON).should('exist').and('contain.text', 'threat.indicator.type'); }); }); diff --git a/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts b/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts index 2df7b88f1607b..f33034dccb9c5 100644 --- a/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts +++ b/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts @@ -11,7 +11,12 @@ import type { UrlObject } from 'url'; import * as yaml from 'js-yaml'; import type { ROLES } from './privileges'; -import { hostDetailsUrl, LOGOUT_URL } from './navigation'; + +const LOGIN_API_ENDPOINT = '/internal/security/login'; +const LOGOUT_URL = '/logout'; + +export const hostDetailsUrl = (hostName: string) => + `/app/security/hosts/${hostName}/authentications`; /** * Credentials in the `kibana.dev.yml` config file will be used to authenticate @@ -43,11 +48,6 @@ const ELASTICSEARCH_USERNAME = 'ELASTICSEARCH_USERNAME'; */ const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; -/** - * The Kibana server endpoint used for authentication - */ -const LOGIN_API_ENDPOINT = '/internal/security/login'; - /** * cy.visit will default to the baseUrl which uses the default kibana test user * This function will override that functionality in cy.visit by building the baseUrl diff --git a/x-pack/plugins/threat_intelligence/cypress/tasks/navigation.ts b/x-pack/plugins/threat_intelligence/cypress/tasks/navigation.ts deleted file mode 100644 index 741a2cf761e8c..0000000000000 --- a/x-pack/plugins/threat_intelligence/cypress/tasks/navigation.ts +++ /dev/null @@ -1,20 +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 const INTEGRATIONS = 'app/integrations#/'; -export const FLEET = 'app/fleet/'; -export const LOGIN_API_ENDPOINT = '/internal/security/login'; -export const LOGOUT_API_ENDPOINT = '/api/security/logout'; -export const LOGIN_URL = '/login'; -export const LOGOUT_URL = '/logout'; - -export const hostDetailsUrl = (hostName: string) => - `/app/security/hosts/${hostName}/authentications`; - -export const navigateTo = (page: string) => { - cy.visit(page); -}; diff --git a/x-pack/plugins/threat_intelligence/cypress/tasks/select_range.ts b/x-pack/plugins/threat_intelligence/cypress/tasks/select_range.ts index 8bf94c7f920ee..aa654a7c300d3 100644 --- a/x-pack/plugins/threat_intelligence/cypress/tasks/select_range.ts +++ b/x-pack/plugins/threat_intelligence/cypress/tasks/select_range.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { FIELD_BROWSER, TIME_RANGE_PICKER } from '../screens/indicators'; +import { TIME_RANGE_PICKER } from '../screens/indicators'; export const selectRange = () => { - cy.get(FIELD_BROWSER); + cy.get(`[data-test-subj="indicatorsTableEmptyState"]`); cy.get(TIME_RANGE_PICKER).first().click({ force: true }); cy.get('[aria-label="Time unit"]').select('y'); diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/mock_field_type_map.ts b/x-pack/plugins/threat_intelligence/public/common/mocks/mock_field_type_map.ts index 39a03139f30a4..90c8da120501e 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/mock_field_type_map.ts +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/mock_field_type_map.ts @@ -5,10 +5,12 @@ * 2.0. */ +import { FieldTypesContextValue } from '../../containers/field_types_provider'; + /** * Mock to map an indicator field to its type. */ -export const generateFieldTypeMap = (): { [id: string]: string } => ({ +export const generateFieldTypeMap = (): FieldTypesContextValue => ({ '@timestamp': 'date', 'threat.indicator.ip': 'ip', 'threat.indicator.first_seen': 'date', diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx index 6da13f89d0887..a9d808d56be93 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx @@ -5,13 +5,19 @@ * 2.0. */ -import React, { ReactNode, VFC } from 'react'; +import React, { FC, ReactNode, VFC } from 'react'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { IUiSettingsClient } from '@kbn/core/public'; +import { CoreStart, IUiSettingsClient } from '@kbn/core/public'; import { TimelinesUIStart } from '@kbn/timelines-plugin/public'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { SecuritySolutionContext } from '../../containers/security_solution_context'; import { getSecuritySolutionContextMock } from './mock_security_context'; +import { FieldTypesContext } from '../../containers/field_types_provider'; +import { generateFieldTypeMap } from './mock_field_type_map'; +import { mockUiSettingsService } from './mock_kibana_ui_settings_service'; +import { mockKibanaTimelinesService } from './mock_kibana_timelines_service'; +import { mockTriggersActionsUiService } from './mock_kibana_triggers_actions_ui_service'; export interface KibanaContextMock { /** @@ -30,29 +36,54 @@ export interface KibanaContextMock { export interface StoryProvidersComponentProps { /** - * Used to generate a new KibanaReactContext (using {@link createKibanaReactContext}) + * Extend / override mock services specified in {@link defaultServices} to create KibanaReactContext (using {@link createKibanaReactContext}). This is optional. */ - kibana: KibanaContextMock; + kibana?: KibanaContextMock; /** * Component(s) to be displayed inside */ children: ReactNode; } +const securityLayout = { + getPluginWrapper: + (): FC => + ({ children }) => +
{children}
, +}; + +const defaultServices = { + uiSettings: mockUiSettingsService(), + timelines: mockKibanaTimelinesService, + triggersActionsUi: mockTriggersActionsUiService, + storage: { + set: () => {}, + get: () => {}, + }, +} as unknown as CoreStart; + /** * Helper functional component used in Storybook stories. * Wraps the story with our {@link SecuritySolutionContext} and KibanaReactContext. */ export const StoryProvidersComponent: VFC = ({ children, - kibana, + kibana = {}, }) => { - const KibanaReactContext = createKibanaReactContext(kibana); + const KibanaReactContext = createKibanaReactContext({ + ...defaultServices, + ...kibana, + securityLayout, + }); const securitySolutionContextMock = getSecuritySolutionContextMock(); return ( - - {children} - + + + + {children} + + + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx index 8648169dfdb4a..d3a94bbcce96e 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx @@ -15,6 +15,7 @@ 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 { createTGridMocks } from '@kbn/timelines-plugin/public/mock'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { KibanaContext } from '../../hooks/use_kibana'; import { SecuritySolutionPluginContext } from '../../types'; import { getSecuritySolutionContextMock } from './mock_security_context'; @@ -22,6 +23,8 @@ import { mockUiSetting } from './mock_kibana_ui_settings_service'; import { SecuritySolutionContext } from '../../containers/security_solution_context'; import { IndicatorsFiltersContext } from '../../modules/indicators/context'; import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context'; +import { FieldTypesContext } from '../../containers/field_types_provider'; +import { generateFieldTypeMap } from './mock_field_type_map'; export const localStorageMock = (): IStorage => { let store: Record = {}; @@ -122,15 +125,19 @@ export const mockedServices = { }; export const TestProvidersComponent: FC = ({ children }) => ( - - - - - {children} - - - - + + + + + + + {children} + + + + + + ); export type MockedSearch = jest.Mocked; diff --git a/x-pack/plugins/threat_intelligence/public/components/layout/layout.stories.tsx b/x-pack/plugins/threat_intelligence/public/components/layout/layout.stories.tsx index e6b73615c61b2..5776af2846688 100644 --- a/x-pack/plugins/threat_intelligence/public/components/layout/layout.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/components/layout/layout.stories.tsx @@ -9,17 +9,22 @@ import React from 'react'; import { Story } from '@storybook/react'; import { EuiText } from '@elastic/eui'; import { DefaultPageLayout } from './layout'; +import { StoryProvidersComponent } from '../../common/mocks/story_providers'; export default { - component: DefaultPageLayout, title: 'DefaultPageLayout', + component: DefaultPageLayout, }; export const Default: Story = () => { const title = 'Title with border below'; const children = Content with border above; - return ; + return ( + + + + ); }; export const NoBorder: Story = () => { @@ -27,5 +32,9 @@ export const NoBorder: Story = () => { const border = false; const children = Content without border; - return ; + return ( + + + + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/containers/field_types_provider.tsx b/x-pack/plugins/threat_intelligence/public/containers/field_types_provider.tsx new file mode 100644 index 0000000000000..050ecb4a3fe10 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/containers/field_types_provider.tsx @@ -0,0 +1,33 @@ +/* + * 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, { createContext, useMemo } from 'react'; +import { FC } from 'react'; +import { useSourcererDataView } from '../modules/indicators/hooks/use_sourcerer_data_view'; + +export type FieldTypesContextValue = Record; + +export const FieldTypesContext = createContext({}); + +/** + * Exposes mapped field types for threat intel shared use + */ +export const FieldTypesProvider: FC = ({ children }) => { + const { indexPattern } = useSourcererDataView(); + + // field name to field type map to allow the cell_renderer to format dates + const fieldTypes: FieldTypesContextValue = useMemo( + () => + indexPattern.fields.reduce((acc, field) => { + acc[field.name] = field.type; + return acc; + }, {} as FieldTypesContextValue), + [indexPattern.fields] + ); + + return {children}; +}; diff --git a/x-pack/plugins/threat_intelligence/public/hooks/use_field_types.ts b/x-pack/plugins/threat_intelligence/public/hooks/use_field_types.ts new file mode 100644 index 0000000000000..66ae2456e2dcf --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/hooks/use_field_types.ts @@ -0,0 +1,13 @@ +/* + * 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 { FieldTypesContext } from '../containers/field_types_provider'; + +export const useFieldTypes = () => { + return useContext(FieldTypesContext) || {}; +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/empty_page/empty_page.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/empty_page/empty_page.stories.tsx index d284fe24052bc..bc298ef95eecf 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/empty_page/empty_page.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/empty_page/empty_page.stories.tsx @@ -6,9 +6,8 @@ */ import React from 'react'; -import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; -import { CoreStart } from '@kbn/core/public'; import { EmptyPage } from '.'; +import { StoryProvidersComponent } from '../../common/mocks/story_providers'; export default { component: BasicEmptyPage, @@ -16,7 +15,7 @@ export default { }; export function BasicEmptyPage() { - const KibanaReactContext = createKibanaReactContext({ + const kibana = { http: { basePath: { get: () => '', @@ -29,10 +28,11 @@ export function BasicEmptyPage() { }, }, }, - } as unknown as Partial); + }; + return ( - + - + ); } diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/indicator_field.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/indicator_field.stories.tsx deleted file mode 100644 index 467ff2ea16cf3..0000000000000 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/indicator_field.stories.tsx +++ /dev/null @@ -1,53 +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 { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; -import { generateFieldTypeMap } from '../../../../common/mocks/mock_field_type_map'; -import { mockUiSettingsService } from '../../../../common/mocks/mock_kibana_ui_settings_service'; -import { generateMockIndicator } from '../../../../../common/types/indicator'; -import { IndicatorField } from './indicator_field'; - -export default { - component: IndicatorField, - title: 'IndicatorField', -}; - -const mockIndicator = generateMockIndicator(); - -const mockFieldTypesMap = generateFieldTypeMap(); - -export function Default() { - const mockField = 'threat.indicator.ip'; - - return ( - - ); -} - -export function IncorrectField() { - const mockField = 'abc'; - - return ( - - ); -} - -export function HandlesDates() { - const KibanaReactContext = createKibanaReactContext({ uiSettings: mockUiSettingsService() }); - const mockField = 'threat.indicator.first_seen'; - - return ( - - - - ); -} diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/index.tsx new file mode 100644 index 0000000000000..a2f2520c9541b --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/index.tsx @@ -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 './indicator_field_label'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/indicator_field_label.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/indicator_field_label.tsx new file mode 100644 index 0000000000000..64e85bc8c5d7e --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/indicator_field_label.tsx @@ -0,0 +1,71 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { RawIndicatorFieldId } from '../../../../../common/types/indicator'; + +interface IndicatorFieldLabelProps { + field: string; +} + +/** + * Renders field label using i18n, or the field key if the translation is not available + */ +export const IndicatorFieldLabel: VFC = ({ field }) => ( + <>{translateFieldLabel(field)} +); + +/** This translates the field name using kbn-i18n */ +export const translateFieldLabel = (field: string) => { + // This switch is necessary as i18n id cannot be dynamic, see: + // https://github.com/elastic/kibana/blob/main/src/dev/i18n/README.md + switch (field) { + case RawIndicatorFieldId.TimeStamp: { + return i18n.translate('xpack.threatIntelligence.field.@timestamp', { + defaultMessage: '@timestamp', + }); + } + case RawIndicatorFieldId.Name: { + return i18n.translate('xpack.threatIntelligence.field.threat.indicator.name', { + defaultMessage: 'Indicator', + }); + } + case RawIndicatorFieldId.Type: { + return i18n.translate('xpack.threatIntelligence.field.threat.indicator.type', { + defaultMessage: 'Indicator type', + }); + } + case RawIndicatorFieldId.Feed: { + return i18n.translate('xpack.threatIntelligence.field.threat.feed.name', { + defaultMessage: 'Feed', + }); + } + case RawIndicatorFieldId.FirstSeen: { + return i18n.translate('xpack.threatIntelligence.field.threat.indicator.first_seen', { + defaultMessage: 'First seen', + }); + } + case RawIndicatorFieldId.LastSeen: { + return i18n.translate('xpack.threatIntelligence.field.threat.indicator.last_seen', { + defaultMessage: 'Last seen', + }); + } + case RawIndicatorFieldId.Confidence: { + return i18n.translate('xpack.threatIntelligence.field.threat.indicator.confidence', { + defaultMessage: 'Confidence', + }); + } + case RawIndicatorFieldId.MarkingTLP: { + return i18n.translate('xpack.threatIntelligence.field.threat.indicator.marking.tlp', { + defaultMessage: 'TLP Marking', + }); + } + default: + return field; + } +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/__snapshots__/indicator_field.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/__snapshots__/indicator_field.test.tsx.snap similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/__snapshots__/indicator_field.test.tsx.snap rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/__snapshots__/indicator_field.test.tsx.snap diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/index.tsx new file mode 100644 index 0000000000000..724caf3c75243 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/index.tsx @@ -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 './indicator_field_value'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.stories.tsx new file mode 100644 index 0000000000000..9548691e49ec5 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.stories.tsx @@ -0,0 +1,40 @@ +/* + * 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 { StoryProvidersComponent } from '../../../../common/mocks/story_providers'; +import { generateMockIndicator } from '../../../../../common/types/indicator'; +import { IndicatorFieldValue } from './indicator_field_value'; + +export default { + component: IndicatorFieldValue, + title: 'IndicatorFieldValue', +}; + +const mockIndicator = generateMockIndicator(); + +export function Default() { + const mockField = 'threat.indicator.ip'; + + return ; +} + +export function IncorrectField() { + const mockField = 'abc'; + + return ; +} + +export function HandlesDates() { + const mockField = 'threat.indicator.first_seen'; + + return ( + + + + ); +} diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/indicator_field.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.test.tsx similarity index 63% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/indicator_field.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.test.tsx index 0cd77b95b7b1a..c695a2c4ebe84 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/indicator_field.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.test.tsx @@ -7,39 +7,25 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { IndicatorField } from './indicator_field'; +import { IndicatorFieldValue } from './indicator_field_value'; import { generateMockIndicator } from '../../../../../common/types/indicator'; import { EMPTY_VALUE } from '../../../../../common/constants'; import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; -import { generateFieldTypeMap } from '../../../../common/mocks/mock_field_type_map'; const mockIndicator = generateMockIndicator(); -const mockFieldTypesMap = generateFieldTypeMap(); describe('', () => { beforeEach(() => {}); it('should return non formatted value', () => { const mockField = 'threat.indicator.ip'; - const component = render( - - ); + const component = render(); expect(component).toMatchSnapshot(); }); it(`should return ${EMPTY_VALUE}`, () => { const mockField = 'abc'; - const component = render( - - ); + const component = render(); expect(component).toMatchSnapshot(); }); @@ -47,11 +33,7 @@ describe('', () => { const mockField = 'threat.indicator.first_seen'; const component = render( - + ); expect(component).toMatchSnapshot(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/indicator_field.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field_value.tsx similarity index 80% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/indicator_field.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field_value.tsx index 6ea779c28be29..c0b46cd1b44b0 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/indicator_field.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field_value.tsx @@ -6,12 +6,13 @@ */ import React, { VFC } from 'react'; +import { useFieldTypes } from '../../../../hooks/use_field_types'; import { EMPTY_VALUE } from '../../../../../common/constants'; import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indicator'; import { DateFormatter } from '../../../../components/date_formatter'; import { unwrapValue } from '../../lib/unwrap_value'; -export interface IndicatorFieldProps { +export interface IndicatorFieldValueProps { /** * Indicator to display the field value from (see {@link Indicator}). */ @@ -20,19 +21,16 @@ export interface IndicatorFieldProps { * The field to get the indicator's value for. */ field: string; - /** - * An object to know what type the field is ('file', 'date', ...). - */ - fieldTypesMap: { [id: string]: string }; } /** * Takes an indicator object, a field and a field => type object to returns the correct value to display. * @returns If the type is a 'date', returns the {@link DateFormatter} component, else returns the value or {@link EMPTY_VALUE}. */ -export const IndicatorField: VFC = ({ indicator, field, fieldTypesMap }) => { +export const IndicatorFieldValue: VFC = ({ indicator, field }) => { + const fieldType = useFieldTypes()[field]; + const value = unwrapValue(indicator, field as RawIndicatorFieldId); - const fieldType = fieldTypesMap[field]; return fieldType === 'date' ? ( ) : value ? ( diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/index.tsx new file mode 100644 index 0000000000000..1e268b953ef09 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/index.tsx @@ -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 './indicator_value_actions'; 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 new file mode 100644 index 0000000000000..94ec2cf2726cd --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_value_actions/indicator_value_actions.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 type { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; +import React, { VFC } from 'react'; +import { EMPTY_VALUE } from '../../../../../common/constants'; +import { Indicator } from '../../../../../common/types/indicator'; +import { FilterInOut } from '../../../query_bar/components/filter_in_out'; +import { AddToTimeline } from '../../../timeline/components/add_to_timeline'; +import { getIndicatorFieldAndValue } from '../../lib/field_value'; + +interface IndicatorValueActions { + indicator: Indicator; + Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; + field: string; + testId?: string; +} + +export const IndicatorValueActions: VFC = ({ + indicator, + field, + testId, + Component, +}) => { + const { key, value } = getIndicatorFieldAndValue(indicator, field); + + if (!key || value === EMPTY_VALUE || !key) { + return null; + } + + return ( + <> + + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/index.tsx new file mode 100644 index 0000000000000..a2b896781739c --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/index.tsx @@ -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 './indicator_empty_prompt'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.stories.tsx new file mode 100644 index 0000000000000..56d66781d187d --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.stories.tsx @@ -0,0 +1,24 @@ +/* + * 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 { IndicatorEmptyPrompt } from './indicator_empty_prompt'; + +export default { + component: IndicatorEmptyPrompt, + title: 'IndicatorEmptyPrompt', +}; + +export const Default: Story = () => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.tsx new file mode 100644 index 0000000000000..0edf3e67f3c03 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_empty_prompt/indicator_empty_prompt.tsx @@ -0,0 +1,37 @@ +/* + * 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 { EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { VFC } from 'react'; + +export const EMPTY_PROMPT_TEST_ID = 'indicatorEmptyPrompt'; + +export const IndicatorEmptyPrompt: VFC = () => ( + + + + } + body={ +

+ +

+ } + data-test-subj={EMPTY_PROMPT_TEST_ID} + /> +); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/index.tsx new file mode 100644 index 0000000000000..9252945c8f552 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/index.tsx @@ -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 './indicator_fields_table'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.stories.tsx new file mode 100644 index 0000000000000..c867eda97389f --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.stories.tsx @@ -0,0 +1,34 @@ +/* + * 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 { mockIndicatorsFiltersContext } from '../../../../../../common/mocks/mock_indicators_filters_context'; +import { IndicatorFieldsTable } from './indicator_fields_table'; +import { generateMockIndicator } from '../../../../../../../common/types/indicator'; +import { StoryProvidersComponent } from '../../../../../../common/mocks/story_providers'; +import { IndicatorsFiltersContext } from '../../../../context'; + +export default { + component: IndicatorFieldsTable, + title: 'IndicatorFieldsTable', +}; + +export function WithIndicators() { + const indicator = generateMockIndicator(); + + return ( + + + + + + ); +} diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.tsx new file mode 100644 index 0000000000000..846417c8cd021 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/components/indicator_fields_table/indicator_fields_table.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 { EuiBasicTableColumn, EuiInMemoryTable, EuiInMemoryTableProps } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo, VFC } from 'react'; +import { Indicator } from '../../../../../../../common/types/indicator'; +import { IndicatorFieldValue } from '../../../indicator_field_value'; +import { IndicatorValueActions } from '../../../indicator_value_actions'; + +export interface IndicatorFieldsTableProps { + fields: string[]; + indicator: Indicator; + search: EuiInMemoryTableProps['search']; + ['data-test-subj']?: string; +} + +export const IndicatorFieldsTable: VFC = ({ + fields, + indicator, + ...rest +}) => { + const columns = useMemo( + () => + [ + { + name: ( + + ), + render: (field: string) => field, + }, + { + name: ( + + ), + render: (field: string) => , + }, + { + actions: [ + { + render: (field: string) => ( + + ), + width: '72px', + }, + ], + }, + ] as Array>, + [indicator] + ); + + return ; +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.stories.tsx index 5076b1649635e..ec87259c90a58 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.stories.tsx @@ -10,7 +10,6 @@ import { Story } from '@storybook/react'; import { CoreStart } from '@kbn/core/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indicators_filters_context'; -import { generateFieldTypeMap } from '../../../../common/mocks/mock_field_type_map'; import { mockUiSettingsService } from '../../../../common/mocks/mock_kibana_ui_settings_service'; import { mockKibanaTimelinesService } from '../../../../common/mocks/mock_kibana_timelines_service'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; @@ -30,14 +29,12 @@ const KibanaReactContext = createKibanaReactContext(coreMock); export const Default: Story = () => { const mockIndicator: Indicator = generateMockIndicator(); - const mockFieldTypesMap = generateFieldTypeMap(); return ( window.alert('Closing flyout')} /> @@ -51,7 +48,6 @@ export const EmptyIndicator: Story = () => { window.alert('Closing flyout')} /> diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.test.tsx index 4ad5c0e5f038a..08add53bafcb1 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/indicators_flyout.test.tsx @@ -6,66 +6,57 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { cleanup, render, screen } from '@testing-library/react'; import { IndicatorsFlyout, SUBTITLE_TEST_ID, TITLE_TEST_ID } from './indicators_flyout'; -import { generateMockIndicator, RawIndicatorFieldId } from '../../../../../common/types/indicator'; -import { EMPTY_VALUE } from '../../../../../common/constants'; -import { dateFormatter } from '../../../../common/utils/dates'; -import { mockUiSetting } from '../../../../common/mocks/mock_kibana_ui_settings_service'; +import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; -import { generateFieldTypeMap } from '../../../../common/mocks/mock_field_type_map'; -import { unwrapValue } from '../../lib/unwrap_value'; const mockIndicator = generateMockIndicator(); -const mockFieldTypesMap = generateFieldTypeMap(); describe('', () => { - it('should render ioc id in title and first_seen in subtitle', () => { - const { getByTestId } = render( + beforeEach(() => { + render( - {}} - /> + {}} /> ); - - expect(getByTestId(TITLE_TEST_ID).innerHTML).toContain( - `Indicator: ${unwrapValue(mockIndicator, RawIndicatorFieldId.Name)}` - ); - expect(getByTestId(SUBTITLE_TEST_ID).innerHTML).toContain( - `First seen: ${dateFormatter( - unwrapValue(mockIndicator, RawIndicatorFieldId.FirstSeen) as string, - mockUiSetting('dateFormat:tz') as string, - mockUiSetting('dateFormat') as string - )}` - ); }); - it(`should render ${EMPTY_VALUE} in on invalid indicator first_seen value`, () => { - const { getByTestId } = render( - - {}} /> - - ); + it('should render all the tab switches', () => { + expect(screen.queryByTestId('tiIndicatorFlyoutTabs')).toBeInTheDocument(); - expect(getByTestId(TITLE_TEST_ID).innerHTML).toContain(`Indicator: ${EMPTY_VALUE}`); - expect(getByTestId(SUBTITLE_TEST_ID).innerHTML).toContain(`First seen: ${EMPTY_VALUE}`); - }); + const switchElement = screen.getByTestId('tiIndicatorFlyoutTabs'); - it(`should render ${EMPTY_VALUE} in title and subtitle on invalid indicator`, () => { - const { getByTestId } = render( - - {}} - /> - - ); + expect(switchElement).toHaveTextContent(/Overview/); + expect(switchElement).toHaveTextContent(/Table/); + expect(switchElement).toHaveTextContent(/JSON/); + }); - expect(getByTestId(TITLE_TEST_ID).innerHTML).toContain(`Indicator: ${EMPTY_VALUE}`); - expect(getByTestId(SUBTITLE_TEST_ID).innerHTML).toContain(`First seen: ${EMPTY_VALUE}`); + describe('title and subtitle', () => { + describe('valid indicator', () => { + it('should render correct title and subtitle', async () => { + expect(screen.getByTestId(TITLE_TEST_ID)).toHaveTextContent('Indicator details'); + }); + }); + + describe('invalid indicator', () => { + beforeEach(() => { + cleanup(); + + render( + + {}} + /> + + ); + }); + + it('should render correct labels', () => { + expect(screen.getByTestId(TITLE_TEST_ID)).toHaveTextContent('Indicator details'); + expect(screen.getByTestId(SUBTITLE_TEST_ID)).toHaveTextContent('First seen: -'); + }); + }); }); }); 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 f1dde44d05aab..4026980ae1c89 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 @@ -19,17 +19,18 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { DateFormatter } from '../../../../components/date_formatter/date_formatter'; -import { EMPTY_VALUE } from '../../../../../common/constants'; import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indicator'; -import { IndicatorsFlyoutJson } from '../indicators_flyout_json/indicators_flyout_json'; -import { IndicatorsFlyoutTable } from '../indicators_flyout_table/indicators_flyout_table'; +import { IndicatorsFlyoutJson } from './tabs/indicators_flyout_json/indicators_flyout_json'; +import { IndicatorsFlyoutTable } from './tabs/indicators_flyout_table/indicators_flyout_table'; import { unwrapValue } from '../../lib/unwrap_value'; +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'; const enum TAB_IDS { + overview, table, json, } @@ -39,10 +40,6 @@ export interface IndicatorsFlyoutProps { * Indicator passed down to the different tabs (table and json views). */ indicator: Indicator; - /** - * Object mapping each field with their type to ease display in the {@link IndicatorsFlyoutTable} component. - */ - fieldTypesMap: { [id: string]: string }; /** * Event to close flyout (used by {@link EuiFlyout}). */ @@ -52,15 +49,26 @@ export interface IndicatorsFlyoutProps { /** * Leverages the {@link EuiFlyout} from the @elastic/eui library to dhow the details of a specific {@link Indicator}. */ -export const IndicatorsFlyout: VFC = ({ - indicator, - fieldTypesMap, - closeFlyout, -}) => { - const [selectedTabId, setSelectedTabId] = useState(TAB_IDS.table); +export const IndicatorsFlyout: VFC = ({ indicator, closeFlyout }) => { + const [selectedTabId, setSelectedTabId] = useState(TAB_IDS.overview); const tabs = useMemo( () => [ + { + id: TAB_IDS.overview, + name: ( + + ), + content: ( + setSelectedTabId(TAB_IDS.table)} + /> + ), + }, { id: TAB_IDS.table, name: ( @@ -69,7 +77,7 @@ export const IndicatorsFlyout: VFC = ({ defaultMessage="Table" /> ), - content: , + content: , }, { id: TAB_IDS.json, @@ -82,7 +90,7 @@ export const IndicatorsFlyout: VFC = ({ content: , }, ], - [indicator, fieldTypesMap] + [indicator] ); const onSelectedTabChanged = (id: number) => setSelectedTabId(id); @@ -102,7 +110,7 @@ export const IndicatorsFlyout: VFC = ({ ); const firstSeen: string = unwrapValue(indicator, RawIndicatorFieldId.FirstSeen) as string; - const displayNameValue = unwrapValue(indicator, RawIndicatorFieldId.Name) || EMPTY_VALUE; + const flyoutTitleId = useGeneratedHtmlId({ prefix: 'simpleFlyoutTitle', }); @@ -113,9 +121,8 @@ export const IndicatorsFlyout: VFC = ({

diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/index.tsx similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/index.tsx diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/indicators_flyout_json.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.stories.tsx similarity index 96% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/indicators_flyout_json.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.stories.tsx index 447dcfcdda6ee..1e40c23a26d4d 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/indicators_flyout_json.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.stories.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Story } from '@storybook/react'; -import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; +import { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; import { IndicatorsFlyoutJson } from './indicators_flyout_json'; export default { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/indicators_flyout_json.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.test.tsx similarity index 82% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/indicators_flyout_json.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.test.tsx index 3310a4e1c893c..a468db60a023e 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/indicators_flyout_json.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.test.tsx @@ -7,16 +7,13 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; -import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; -import { - CODE_BLOCK_TEST_ID, - EMPTY_PROMPT_TEST_ID, - IndicatorsFlyoutJson, -} from './indicators_flyout_json'; +import { TestProvidersComponent } from '../../../../../../common/mocks/test_providers'; +import { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; +import { CODE_BLOCK_TEST_ID, IndicatorsFlyoutJson } from './indicators_flyout_json'; const mockIndicator: Indicator = generateMockIndicator(); +import { EMPTY_PROMPT_TEST_ID } from '../../components/indicator_empty_prompt'; describe('', () => { it('should render code block component on valid indicator', () => { const { getByTestId } = render( diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/indicators_flyout_json.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.tsx similarity index 52% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/indicators_flyout_json.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.tsx index 6bc42b1980c20..99c4bcfb0d50f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_json/indicators_flyout_json.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_json/indicators_flyout_json.tsx @@ -6,11 +6,10 @@ */ import React, { VFC } from 'react'; -import { EuiCodeBlock, EuiEmptyPrompt } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { Indicator } from '../../../../../common/types/indicator'; +import { EuiCodeBlock } from '@elastic/eui'; +import { Indicator } from '../../../../../../../common/types/indicator'; +import { IndicatorEmptyPrompt } from '../../components/indicator_empty_prompt'; -export const EMPTY_PROMPT_TEST_ID = 'tiFlyoutJsonEmptyPrompt'; export const CODE_BLOCK_TEST_ID = 'tiFlyoutJsonCodeBlock'; export interface IndicatorsFlyoutJsonProps { @@ -26,27 +25,7 @@ export interface IndicatorsFlyoutJsonProps { */ export const IndicatorsFlyoutJson: VFC = ({ indicator }) => { return Object.keys(indicator).length === 0 ? ( - - - - } - body={ -

- -

- } - data-test-subj={EMPTY_PROMPT_TEST_ID} - /> + ) : ( {JSON.stringify(indicator, null, 2)} diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/index.tsx similarity index 87% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/index.tsx index acea79493c0be..dbad4c02ee5dc 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field/index.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/index.tsx @@ -5,4 +5,4 @@ * 2.0. */ -export * from './indicator_field'; +export * from './indicator_block'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.stories.tsx new file mode 100644 index 0000000000000..32966e72c2eec --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.stories.tsx @@ -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 React from 'react'; +import { IndicatorsFiltersContext } from '../../../../../../context'; +import { StoryProvidersComponent } from '../../../../../../../../common/mocks/story_providers'; +import { generateMockIndicator } from '../../../../../../../../../common/types/indicator'; +import { IndicatorBlock } from './indicator_block'; + +export default { + component: IndicatorBlock, + title: 'IndicatorBlock', +}; + +const mockIndicator = generateMockIndicator(); + +export function Default() { + const mockField = 'threat.indicator.ip'; + + return ( + + + + + + ); +} 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 new file mode 100644 index 0000000000000..a6f5c5a387c17 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/block/indicator_block.tsx @@ -0,0 +1,69 @@ +/* + * 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 { EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import React, { VFC } from 'react'; +import { euiStyled, css } from '@kbn/kibana-react-plugin/common'; +import { Indicator } from '../../../../../../../../../common/types/indicator'; +import { IndicatorFieldValue } from '../../../../../indicator_field_value'; +import { IndicatorFieldLabel } from '../../../../../indicator_field_label'; +import { IndicatorValueActions } from '../../../../../indicator_value_actions'; + +/** + * Show actions wrapper on hover. This is a helper component, limited only to Block + */ +const VisibleOnHover = euiStyled.div` + ${({ theme }) => css` + & { + height: 100%; + } + + & .actionsWrapper { + visibility: hidden; + display: inline-block; + margin-inline-start: ${theme.eui.euiSizeXS}; + } + + &:hover .actionsWrapper { + visibility: visible; + } + `} +`; + +const panelProps = { + color: 'subdued' as const, + hasShadow: false, + borderRadius: 'none' as const, + paddingSize: 's' as const, +}; + +export interface IndicatorBlockProps { + indicator: Indicator; + field: string; +} + +/** + * Renders indicator field value in a rectangle, to highlight it even more + */ +export const IndicatorBlock: VFC = ({ field, indicator }) => { + return ( + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx new file mode 100644 index 0000000000000..d7cf25dca3239 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/highlighted_values_table.tsx @@ -0,0 +1,54 @@ +/* + * 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, { useMemo, VFC } from 'react'; +import { Indicator, RawIndicatorFieldId } from '../../../../../../../../../common/types/indicator'; +import { unwrapValue } from '../../../../../../lib/unwrap_value'; +import { IndicatorFieldsTable } from '../../../../components/indicator_fields_table'; + +/** + * Pick indicator fields starting with the indicator type + */ +const byIndicatorType = (indicatorType: string) => (field: string) => + field.startsWith(`threat.indicator.${indicatorType}`) || + [ + 'threat.indicator.reference', + 'threat.indicator.description', + 'threat.software.alias', + 'threat.indicator.confidence', + 'threat.tactic.name', + 'threat.tactic.reference', + ].includes(field); + +interface HighlightedValuesTableProps { + indicator: Indicator; + ['data-test-subj']?: string; +} + +/** + * Displays highlighted indicator values based on indicator type + */ +export const HighlightedValuesTable: VFC = ({ + indicator, + ...props +}) => { + const indicatorType = unwrapValue(indicator, RawIndicatorFieldId.Type); + + const highlightedFields: string[] = useMemo( + () => Object.keys(indicator.fields).filter(byIndicatorType(indicatorType || '')), + [indicator.fields, indicatorType] + ); + + return ( + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/index.tsx new file mode 100644 index 0000000000000..d31d18ec79367 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/components/highlighted_values_table/index.tsx @@ -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 './highlighted_values_table'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/index.tsx new file mode 100644 index 0000000000000..71fcb871adf42 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/index.tsx @@ -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 './indicators_flyout_overview'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.stories.tsx new file mode 100644 index 0000000000000..72b20f769575b --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.stories.tsx @@ -0,0 +1,36 @@ +/* + * 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 { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; +import { IndicatorsFlyoutOverview } from './indicators_flyout_overview'; +import { IndicatorsFiltersContext } from '../../../../context'; + +export default { + component: IndicatorsFlyoutOverview, + title: 'IndicatorsFlyoutOverview', + parameters: { + backgrounds: { + default: 'white', + values: [{ name: 'white', value: '#fff' }], + }, + }, +}; + +export const Default: Story = () => { + const mockIndicator: Indicator = generateMockIndicator(); + + return ( + + + {}} indicator={mockIndicator} /> + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.test.tsx new file mode 100644 index 0000000000000..580534e5668c2 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.test.tsx @@ -0,0 +1,48 @@ +/* + * 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 { TestProvidersComponent } from '../../../../../../common/mocks/test_providers'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; +import { + IndicatorsFlyoutOverview, + TI_FLYOUT_OVERVIEW_HIGH_LEVEL_BLOCKS, + TI_FLYOUT_OVERVIEW_TABLE, +} from './indicators_flyout_overview'; +import { EMPTY_PROMPT_TEST_ID } from '../../components/indicator_empty_prompt'; + +describe('', () => { + describe('invalid indicator', () => { + it('should render error message on invalid indicator', () => { + render( + + {}} + indicator={{ fields: {} } as unknown as Indicator} + /> + + ); + + expect(screen.getByTestId(EMPTY_PROMPT_TEST_ID)).toBeInTheDocument(); + }); + }); + + it('should render the highlighted blocks and table when valid indicator is passed', () => { + render( + + {}} + indicator={generateMockIndicator()} + /> + + ); + + expect(screen.queryByTestId(TI_FLYOUT_OVERVIEW_TABLE)).toBeInTheDocument(); + expect(screen.queryByTestId(TI_FLYOUT_OVERVIEW_HIGH_LEVEL_BLOCKS)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx new file mode 100644 index 0000000000000..371af7299b3ea --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_overview/indicators_flyout_overview.tsx @@ -0,0 +1,111 @@ +/* + * 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 { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo } from 'react'; +import { VFC } from 'react'; +import { EMPTY_VALUE } from '../../../../../../../common/constants'; +import { Indicator, RawIndicatorFieldId } from '../../../../../../../common/types/indicator'; +import { unwrapValue } from '../../../../lib/unwrap_value'; +import { IndicatorEmptyPrompt } from '../../components/indicator_empty_prompt'; +import { IndicatorBlock } from './components/block'; +import { HighlightedValuesTable } from './components/highlighted_values_table'; + +const highLevelFields = [ + RawIndicatorFieldId.Feed, + RawIndicatorFieldId.Type, + RawIndicatorFieldId.MarkingTLP, + RawIndicatorFieldId.Confidence, +]; + +export const TI_FLYOUT_OVERVIEW_TABLE = 'tiFlyoutOverviewTable'; +export const TI_FLYOUT_OVERVIEW_HIGH_LEVEL_BLOCKS = 'tiFlyoutOverviewHighLevelBlocks'; + +export interface IndicatorsFlyoutOverviewProps { + indicator: Indicator; + onViewAllFieldsInTable: VoidFunction; +} + +export const IndicatorsFlyoutOverview: VFC = ({ + indicator, + onViewAllFieldsInTable, +}) => { + const indicatorType = unwrapValue(indicator, RawIndicatorFieldId.Type); + + const highLevelBlocks = useMemo( + () => ( + + {highLevelFields.map((field) => ( + + + + ))} + + ), + [indicator] + ); + + const indicatorDescription = useMemo(() => { + const unwrappedDescription = unwrapValue(indicator, RawIndicatorFieldId.Description); + + return unwrappedDescription ? {unwrappedDescription} : null; + }, [indicator]); + + const indicatorName = unwrapValue(indicator, RawIndicatorFieldId.Name) || EMPTY_VALUE; + + if (!indicatorType) { + return ; + } + + return ( + <> + +

{indicatorName}

+
+ + {indicatorDescription} + + + + {highLevelBlocks} + + + + + + +
+ +
+
+
+ + + + + +
+ + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/index.tsx similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/index.tsx diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/indicators_flyout_table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.stories.tsx similarity index 62% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/indicators_flyout_table.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.stories.tsx index e864d292c61cb..3a3aa1fa788ee 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/indicators_flyout_table.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.stories.tsx @@ -9,13 +9,12 @@ import React from 'react'; import { Story } from '@storybook/react'; import { CoreStart } from '@kbn/core/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; -import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indicators_filters_context'; -import { generateFieldTypeMap } from '../../../../common/mocks/mock_field_type_map'; -import { mockUiSettingsService } from '../../../../common/mocks/mock_kibana_ui_settings_service'; -import { mockKibanaTimelinesService } from '../../../../common/mocks/mock_kibana_timelines_service'; -import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; +import { mockIndicatorsFiltersContext } from '../../../../../../common/mocks/mock_indicators_filters_context'; +import { mockUiSettingsService } from '../../../../../../common/mocks/mock_kibana_ui_settings_service'; +import { mockKibanaTimelinesService } from '../../../../../../common/mocks/mock_kibana_timelines_service'; +import { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; import { IndicatorsFlyoutTable } from './indicators_flyout_table'; -import { IndicatorsFiltersContext } from '../../context'; +import { IndicatorsFiltersContext } from '../../../../context'; export default { component: IndicatorsFlyoutTable, @@ -24,7 +23,6 @@ export default { export const Default: Story = () => { const mockIndicator: Indicator = generateMockIndicator(); - const mockFieldTypesMap = generateFieldTypeMap(); const KibanaReactContext = createKibanaReactContext({ uiSettings: mockUiSettingsService(), @@ -34,14 +32,12 @@ export const Default: Story = () => { return ( - + ); }; export const EmptyIndicator: Story = () => { - return ( - - ); + return ; }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/indicators_flyout_table.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx similarity index 72% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/indicators_flyout_table.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx index bc7cfffbcf7a3..c91ce0e6aa89c 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/indicators_flyout_table.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.test.tsx @@ -7,28 +7,23 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; +import { TestProvidersComponent } from '../../../../../../common/mocks/test_providers'; import { generateMockIndicator, Indicator, RawIndicatorFieldId, -} from '../../../../../common/types/indicator'; -import { generateFieldTypeMap } from '../../../../common/mocks/mock_field_type_map'; -import { - EMPTY_PROMPT_TEST_ID, - IndicatorsFlyoutTable, - TABLE_TEST_ID, -} from './indicators_flyout_table'; -import { unwrapValue } from '../../lib/unwrap_value'; +} from '../../../../../../../common/types/indicator'; +import { IndicatorsFlyoutTable, TABLE_TEST_ID } from './indicators_flyout_table'; +import { unwrapValue } from '../../../../lib/unwrap_value'; +import { EMPTY_PROMPT_TEST_ID } from '../../components/indicator_empty_prompt'; const mockIndicator: Indicator = generateMockIndicator(); -const mockFieldTypesMap = generateFieldTypeMap(); describe('', () => { it('should render fields and values in table', () => { const { getByTestId, getByText, getAllByText } = render( - + ); @@ -49,7 +44,7 @@ describe('', () => { it('should render error message on invalid indicator', () => { const { getByTestId, getByText } = render( - + ); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.tsx new file mode 100644 index 0000000000000..83bd913e3eedb --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout/tabs/indicators_flyout_table/indicators_flyout_table.tsx @@ -0,0 +1,47 @@ +/* + * 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 { Indicator } from '../../../../../../../common/types/indicator'; +import { IndicatorEmptyPrompt } from '../../components/indicator_empty_prompt'; +import { IndicatorFieldsTable } from '../../components/indicator_fields_table'; + +export const TABLE_TEST_ID = 'tiFlyoutTableMemoryTable'; +export const TIMELINE_BUTTON_TEST_ID = 'tiFlyoutTableRowTimelineButton'; + +const search = { + box: { + incremental: true, + schema: true, + }, +}; + +export interface IndicatorsFlyoutTableProps { + /** + * Indicator to display in table view. + */ + indicator: Indicator; +} + +/** + * Displays all the properties and values of an {@link Indicator} in table view, + * using the {@link EuiInMemoryTable} from the @elastic/eui library. + */ +export const IndicatorsFlyoutTable: VFC = ({ indicator }) => { + const items: string[] = Object.keys(indicator.fields); + + return items.length === 0 ? ( + + ) : ( + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/indicators_flyout_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/indicators_flyout_table.tsx deleted file mode 100644 index c6a64c51629af..0000000000000 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_flyout_table/indicators_flyout_table.tsx +++ /dev/null @@ -1,120 +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 { EuiEmptyPrompt, EuiInMemoryTable } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import React, { VFC } from 'react'; -import { FilterInOut } from '../../../query_bar/components/filter_in_out'; -import { Indicator } from '../../../../../common/types/indicator'; -import { AddToTimeline } from '../../../timeline/components/add_to_timeline'; -import { IndicatorField } from '../indicator_field/indicator_field'; - -export const EMPTY_PROMPT_TEST_ID = 'tiFlyoutTableEmptyPrompt'; -export const TABLE_TEST_ID = 'tiFlyoutTableMemoryTable'; -export const TIMELINE_BUTTON_TEST_ID = 'tiFlyoutTableRowTimelineButton'; - -const search = { - box: { - incremental: true, - schema: true, - }, -}; - -export interface IndicatorsFlyoutTableProps { - /** - * Indicator to display in table view. - */ - indicator: Indicator; - /** - * Object mapping each field with their type to ease display in the {@link IndicatorField} component. - */ - fieldTypesMap: { [id: string]: string }; -} - -/** - * Displays all the properties and values of an {@link Indicator} in table view, - * using the {@link EuiInMemoryTable} from the @elastic/eui library. - */ -export const IndicatorsFlyoutTable: VFC = ({ - indicator, - fieldTypesMap, -}) => { - const items: string[] = Object.keys(indicator.fields); - const columns = [ - { - name: ( - - ), - actions: [ - { - render: (field: string) => ( - <> - - - - ), - }, - ], - width: '72px', - }, - { - name: ( - - ), - render: (field: string) => field, - }, - { - name: ( - - ), - render: (field: string) => ( - - ), - }, - ]; - - return items.length === 0 ? ( - - - - } - body={ -

- -

- } - data-test-subj={EMPTY_PROMPT_TEST_ID} - /> - ) : ( - - ); -}; 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 db3de2eeb67e7..c17d09a4bf7dc 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 @@ -50,11 +50,11 @@ export const CellActions: VFC = ({ return ( <> - + diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx index e0b4e1e88daa0..b95a378a35a5b 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx @@ -11,7 +11,7 @@ import { euiLightVars as themeLight, euiDarkVars as themeDark } from '@kbn/ui-th import React from 'react'; import { useKibana } from '../../../../hooks/use_kibana'; import { Indicator } from '../../../../../common/types/indicator'; -import { IndicatorField } from '../indicator_field/indicator_field'; +import { IndicatorFieldValue } from '../indicator_field_value/indicator_field_value'; import { IndicatorsTableContext } from './context'; import { ActionsRowCell } from './actions_row_cell'; @@ -29,7 +29,7 @@ export const cellRendererFactory = (from: number) => { const darkMode = uiSettings.get('theme:darkMode'); - const { indicators, expanded, fieldTypesMap } = indicatorsTableContext; + const { indicators, expanded } = indicatorsTableContext; const indicator: Indicator | undefined = indicators[rowIndex - from]; @@ -53,6 +53,6 @@ export const cellRendererFactory = (from: number) => { return ; } - return ; + return ; }; }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/context.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/context.ts index bd8c619edfd04..41596a257dc9e 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/context.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/context.ts @@ -12,7 +12,6 @@ export interface IndicatorsTableContextValue { expanded: Indicator | undefined; setExpanded: Dispatch>; indicators: Indicator[]; - fieldTypesMap: { [id: string]: string }; } export const IndicatorsTableContext = createContext( diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.ts index 266ef8811955d..8315f41725ed0 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.ts @@ -7,52 +7,22 @@ import { EuiDataGridColumn } from '@elastic/eui'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { i18n } from '@kbn/i18n'; import negate from 'lodash/negate'; import { RawIndicatorFieldId } from '../../../../../../common/types/indicator'; import { useKibana } from '../../../../../hooks/use_kibana'; +import { translateFieldLabel } from '../../indicator_field_label'; const DEFAULT_COLUMNS: EuiDataGridColumn[] = [ - { - id: RawIndicatorFieldId.TimeStamp, - displayAsText: i18n.translate('xpack.threatIntelligence.indicator.table.timestampColumnTitle', { - defaultMessage: '@timestamp', - }), - }, - { - id: RawIndicatorFieldId.Name, - displayAsText: i18n.translate('xpack.threatIntelligence.indicator.table.indicatorColumTitle', { - defaultMessage: 'Indicator', - }), - }, - { - id: RawIndicatorFieldId.Type, - displayAsText: i18n.translate( - 'xpack.threatIntelligence.indicator.table.indicatorTypeColumTitle', - { - defaultMessage: 'Indicator type', - } - ), - }, - { - id: RawIndicatorFieldId.Feed, - displayAsText: i18n.translate('xpack.threatIntelligence.indicator.table.FeedColumTitle', { - defaultMessage: 'Feed', - }), - }, - { - id: RawIndicatorFieldId.FirstSeen, - displayAsText: i18n.translate('xpack.threatIntelligence.indicator.table.firstSeenColumTitle', { - defaultMessage: 'First seen', - }), - }, - { - id: RawIndicatorFieldId.LastSeen, - displayAsText: i18n.translate('xpack.threatIntelligence.indicator.table.lastSeenColumTitle', { - defaultMessage: 'Last seen', - }), - }, -]; + RawIndicatorFieldId.TimeStamp, + RawIndicatorFieldId.Name, + RawIndicatorFieldId.Type, + RawIndicatorFieldId.Feed, + RawIndicatorFieldId.FirstSeen, + RawIndicatorFieldId.LastSeen, +].map((field) => ({ + id: field, + displayAsText: translateFieldLabel(field), +})); const DEFAULT_VISIBLE_COLUMNS = DEFAULT_COLUMNS.map((column) => column.id); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx index e92df3f388e97..07765dbf601d4 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx @@ -5,14 +5,10 @@ * 2.0. */ -import { CoreStart } from '@kbn/core/public'; -import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; import React from 'react'; import { DataView } from '@kbn/data-views-plugin/common'; import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indicators_filters_context'; -import { mockTriggersActionsUiService } from '../../../../common/mocks/mock_kibana_triggers_actions_ui_service'; -import { mockUiSettingsService } from '../../../../common/mocks/mock_kibana_ui_settings_service'; -import { mockKibanaTimelinesService } from '../../../../common/mocks/mock_kibana_timelines_service'; +import { StoryProvidersComponent } from '../../../../common/mocks/story_providers'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; import { IndicatorsTable } from './indicators_table'; import { IndicatorsFiltersContext } from '../../context'; @@ -29,14 +25,8 @@ const stub = () => void 0; export function WithIndicators() { const indicatorsFixture: Indicator[] = Array(10).fill(generateMockIndicator()); - const KibanaReactContext = createKibanaReactContext({ - uiSettings: mockUiSettingsService(), - timelines: mockKibanaTimelinesService, - triggersActionsUi: mockTriggersActionsUiService, - } as unknown as CoreStart); - return ( - + - + ); } export function WithNoIndicators() { return ( - + + + ); } diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx index 9cd711d313a54..a3604b31cf75d 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx @@ -55,7 +55,6 @@ export const IndicatorsTable: VFC = ({ onChangeItemsPerPage, pagination, loading, - indexPattern, browserFields, }) => { const [expanded, setExpanded] = useState(); @@ -65,18 +64,9 @@ export const IndicatorsTable: VFC = ({ [pagination.pageIndex, pagination.pageSize] ); - // field name to field type map to allow the cell_renderer to format dates - const fieldTypesMap: { [id: string]: string } = useMemo(() => { - if (!indexPattern) return {}; - - const res: { [id: string]: string } = {}; - indexPattern.fields.map((field) => (res[field.name] = field.type)); - return res; - }, [indexPattern]); - const indicatorTableContextValue = useMemo( - () => ({ expanded, setExpanded, indicators, fieldTypesMap }), - [expanded, indicators, fieldTypesMap] + () => ({ expanded, setExpanded, indicators }), + [expanded, indicators] ); const start = pagination.pageIndex * pagination.pageSize; @@ -131,13 +121,9 @@ export const IndicatorsTable: VFC = ({ const flyoutFragment = useMemo( () => expanded ? ( - setExpanded(undefined)} - /> + setExpanded(undefined)} /> ) : null, - [expanded, fieldTypesMap] + [expanded] ); const gridFragment = useMemo(() => { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts index 9ea7e492c7893..82e59dc7b3ad3 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.ts @@ -197,12 +197,12 @@ export const useAggregatedIndicators = ({ ) .subscribe({ next: (response) => { - const aggregations: Aggregation[] = - response.rawResponse.aggregations[AGGREGATION_NAME]?.buckets; - const chartSeries: ChartSeries[] = convertAggregationToChartSeries(aggregations); - setIndicators(chartSeries); - if (isCompleteResponse(response)) { + const aggregations: Aggregation[] = + response.rawResponse.aggregations[AGGREGATION_NAME]?.buckets; + const chartSeries: ChartSeries[] = convertAggregationToChartSeries(aggregations); + setIndicators(chartSeries); + searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { searchSubscription$.current.unsubscribe(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx index f492ac74dcaa3..c3b643df2ec45 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx @@ -15,6 +15,7 @@ import { useFilters } from '../query_bar/hooks/use_filters'; import { FiltersGlobal } from '../../containers/filters_global'; import QueryBar from '../query_bar/components/query_bar'; import { useSourcererDataView } from './hooks/use_sourcerer_data_view'; +import { FieldTypesProvider } from '../../containers/field_types_provider'; export const IndicatorsPage: VFC = () => { const { browserFields, indexPattern } = useSourcererDataView(); @@ -37,33 +38,35 @@ export const IndicatorsPage: VFC = () => { }); return ( - - - - - - - - - + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in_out/filter_in_out.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in_out/filter_in_out.test.tsx index db11ca497b515..71de0fe1e6ab8 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in_out/filter_in_out.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in_out/filter_in_out.test.tsx @@ -38,7 +38,7 @@ describe('', () => { const mockComponent: FunctionComponent = () => ; const component = render( - + ); expect(component).toMatchSnapshot(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in_out/filter_in_out.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in_out/filter_in_out.tsx index 56088c953ef08..0027758505fff 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in_out/filter_in_out.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in_out/filter_in_out.tsx @@ -42,13 +42,13 @@ export interface FilterInOutProps { /** * Display component for when the FilterIn component is used within a DataGrid */ - Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; + as?: typeof EuiButtonEmpty | typeof EuiButtonIcon; } /** * Retrieves the indicator's field and value, then creates a new {@link Filter} and adds it to the {@link FilterManager}. */ -export const FilterInOut: VFC = ({ data, field, Component }) => { +export const FilterInOut: VFC = ({ data, field, as: Component }) => { const styles = useStyles(); const { filterManager } = useIndicatorsFiltersContext(); 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..4a2bbe9be6367 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 @@ -27,7 +27,7 @@ export interface AddToTimelineProps { /** * Only used with `EuiDataGrid` (see {@link AddToTimelineButtonProps}). */ - component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; + as?: typeof EuiButtonEmpty | typeof EuiButtonIcon; /** * Used as `data-test-subj` value for e2e tests. */ @@ -42,7 +42,7 @@ export interface AddToTimelineProps { * * @returns add to timeline button or an empty component. */ -export const AddToTimeline: VFC = ({ data, field, component, testId }) => { +export const AddToTimeline: VFC = ({ data, field, as, testId }) => { const styles = useStyles(); const addToTimelineButton = @@ -78,7 +78,7 @@ export const AddToTimeline: VFC = ({ data, field, component, field: key, ownFocus: false, }; - if (component) addToTimelineProps.Component = component; + if (as) addToTimelineProps.Component = as; return (
diff --git a/x-pack/plugins/threat_intelligence/scripts/generate_indicators.js b/x-pack/plugins/threat_intelligence/scripts/generate_indicators.js index bade9615b630d..44e0ad166353c 100644 --- a/x-pack/plugins/threat_intelligence/scripts/generate_indicators.js +++ b/x-pack/plugins/threat_intelligence/scripts/generate_indicators.js @@ -8,7 +8,7 @@ const { Client } = require('@elastic/elasticsearch'); const faker = require('faker'); -const THREAT_INDEX = 'ti-logs'; +const THREAT_INDEX = 'logs-ti'; /** Drop the index first? */ const CLEANUP_FIRST = true; @@ -82,7 +82,7 @@ const main = async () => { for (let i = 0; i < CHUNK_SIZE; i++) { const RANDOM_OFFSET_WITHIN_ONE_MONTH = Math.floor(Math.random() * 3600 * 24 * 30 * 1000); - const timestamp = Date.now() - RANDOM_OFFSET_WITHIN_ONE_MONTH; + const timestamp = new Date(Date.now() - RANDOM_OFFSET_WITHIN_ONE_MONTH).toISOString(); operations.push( ...[ diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index a7aadfb61248a..01bbf59adb16f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -31395,7 +31395,6 @@ "xpack.synthetics.uptimeSettings.index": "Paramètres Uptime - Index", "xpack.synthetics.waterfallChart.sidebar.url.https": "https", "xpack.threatIntelligence.common.emptyPage.body3": "Pour vous lancer avec Elastic Threat Intelligence, activez une ou plusieurs intégrations Threat Intelligence depuis la page Intégrations ou bien ingérez des données avec Filebeat. Pour plus d'informations, consultez la ressource {docsLink}.", - "xpack.threatIntelligence.indicator.flyout.panelTitle": "Indicateur : {title}", "xpack.threatIntelligence.common.emptyPage.body1": "Elastic Threat Intelligence facilite l'analyse et l'investigation des menaces potentielles pour la sécurité en regroupant les données de plusieurs sources en un seul endroit.", "xpack.threatIntelligence.common.emptyPage.body2": "Vous pourrez consulter les données de tous les flux Threat Intelligence activés et prendre des mesures à partir de cette page.", "xpack.threatIntelligence.common.emptyPage.buttonText": "Ajouter des intégrations", @@ -31406,18 +31405,16 @@ "xpack.threatIntelligence.empty.title": "Aucun résultat ne correspond à vos critères de recherche.", "xpack.threatIntelligence.indicator.flyout.jsonTabLabel": "JSON", "xpack.threatIntelligence.indicator.flyout.tableTabLabel": "Tableau", - "xpack.threatIntelligence.indicator.flyoutJson.errorMessageBody": "Une erreur s'est produite lors de l'affichage des champs et des valeurs des indicateurs.", - "xpack.threatIntelligence.indicator.flyoutJson.errorMessageTitle": "Impossible d'afficher les informations des indicateurs", "xpack.threatIntelligence.indicator.flyoutTable.errorMessageBody": "Une erreur s'est produite lors de l'affichage des champs et des valeurs des indicateurs.", "xpack.threatIntelligence.indicator.flyoutTable.errorMessageTitle": "Impossible d'afficher les informations des indicateurs", - "xpack.threatIntelligence.indicator.flyoutTable.fieldColumnLabel": "Champ", - "xpack.threatIntelligence.indicator.flyoutTable.valueColumnLabel": "Valeur", + "xpack.threatIntelligence.indicator.fieldsTable.fieldColumnLabel": "Champ", + "xpack.threatIntelligence.indicator.fieldsTable.valueColumnLabel": "Valeur", "xpack.threatIntelligence.indicator.table.actionColumnLabel": "Actions", - "xpack.threatIntelligence.indicator.table.FeedColumTitle": "Fil", - "xpack.threatIntelligence.indicator.table.firstSeenColumTitle": "Vu en premier", - "xpack.threatIntelligence.indicator.table.indicatorColumTitle": "Indicateur", - "xpack.threatIntelligence.indicator.table.indicatorTypeColumTitle": "Type d’indicateur", - "xpack.threatIntelligence.indicator.table.lastSeenColumTitle": "Vu en dernier", + "xpack.threatIntelligence.field.threat.feed.name": "Fil", + "xpack.threatIntelligence.field.threat.indicator.first_seen": "Vu en premier", + "xpack.threatIntelligence.field.threat.indicator.name": "Indicateur", + "xpack.threatIntelligence.field.threat.indicator.type": "Type d’indicateur", + "xpack.threatIntelligence.field.threat.indicator.last_seen": "Vu en dernier", "xpack.threatIntelligence.indicator.table.viewDetailsButton": "Afficher les détails", "xpack.timelines.clipboard.copy.successToastTitle": "Champ {field} copié dans le presse-papiers", "xpack.timelines.footer.autoRefreshActiveTooltip": "Lorsque l'actualisation automatique est activée, la chronologie vous montrera les {numberOfItems} derniers événements correspondant à votre recherche.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fd4a320e417e1..f6dbbfc735890 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -31373,7 +31373,6 @@ "xpack.synthetics.uptimeSettings.index": "アップタイム設定 - インデックス", "xpack.synthetics.waterfallChart.sidebar.url.https": "https", "xpack.threatIntelligence.common.emptyPage.body3": "Elastic Threat Intelligenceを開始するには、[統合]ページから1つ以上の脅威インテリジェンス統合を有効にするか、Filebeatを使用してデータを取り込みます。詳細については、{docsLink}をご覧ください。", - "xpack.threatIntelligence.indicator.flyout.panelTitle": "インジケーター:{title}", "xpack.threatIntelligence.common.emptyPage.body1": "Elastic Threat Intelligenceでは、複数のソースのデータを一元的に集約することで、潜在的なセキュリティ脅威を簡単に分析、調査できます。", "xpack.threatIntelligence.common.emptyPage.body2": "すべてのアクティブな脅威インテリジェンスフィードのデータを表示し、このページからアクションを実行できます。", "xpack.threatIntelligence.common.emptyPage.buttonText": "統合の追加", @@ -31384,18 +31383,16 @@ "xpack.threatIntelligence.empty.title": "検索条件と一致する結果がありません。", "xpack.threatIntelligence.indicator.flyout.jsonTabLabel": "JSON", "xpack.threatIntelligence.indicator.flyout.tableTabLabel": "表", - "xpack.threatIntelligence.indicator.flyoutJson.errorMessageBody": "インジケーターフィールドと値の表示エラーが発生しました。", - "xpack.threatIntelligence.indicator.flyoutJson.errorMessageTitle": "インジケーター情報を表示できませn", "xpack.threatIntelligence.indicator.flyoutTable.errorMessageBody": "インジケーターフィールドと値の表示エラーが発生しました。", "xpack.threatIntelligence.indicator.flyoutTable.errorMessageTitle": "インジケーター情報を表示できませn", - "xpack.threatIntelligence.indicator.flyoutTable.fieldColumnLabel": "フィールド", - "xpack.threatIntelligence.indicator.flyoutTable.valueColumnLabel": "値", + "xpack.threatIntelligence.indicator.fieldsTable.fieldColumnLabel": "フィールド", + "xpack.threatIntelligence.indicator.fieldsTable.valueColumnLabel": "値", "xpack.threatIntelligence.indicator.table.actionColumnLabel": "アクション", - "xpack.threatIntelligence.indicator.table.FeedColumTitle": "フィード", - "xpack.threatIntelligence.indicator.table.firstSeenColumTitle": "初回の認識", - "xpack.threatIntelligence.indicator.table.indicatorColumTitle": "インジケーター", - "xpack.threatIntelligence.indicator.table.indicatorTypeColumTitle": "インジケータータイプ", - "xpack.threatIntelligence.indicator.table.lastSeenColumTitle": "前回の認識", + "xpack.threatIntelligence.field.threat.feed.name": "フィード", + "xpack.threatIntelligence.field.threat.indicator.first_seen": "初回の認識", + "xpack.threatIntelligence.field.threat.indicator.name": "インジケーター", + "xpack.threatIntelligence.field.threat.indicator.type": "インジケータータイプ", + "xpack.threatIntelligence.field.threat.indicator.last_seen": "前回の認識", "xpack.threatIntelligence.indicator.table.viewDetailsButton": "詳細を表示", "xpack.timelines.clipboard.copy.successToastTitle": "フィールド{field}をクリップボードにコピーしました", "xpack.timelines.footer.autoRefreshActiveTooltip": "自動更新が有効な間、タイムラインはクエリに一致する最新の {numberOfItems} 件のイベントを表示します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8002605d93758..60c556135b465 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -31405,7 +31405,6 @@ "xpack.synthetics.uptimeSettings.index": "Uptime 设置 - 索引", "xpack.synthetics.waterfallChart.sidebar.url.https": "https", "xpack.threatIntelligence.common.emptyPage.body3": "要开始使用 Elastic 威胁情报,请从“集成”页面启用一个或多个威胁情报集成,或使用 Filebeat 采集数据。有关更多信息,请查看 {docsLink}。", - "xpack.threatIntelligence.indicator.flyout.panelTitle": "指标:{title}", "xpack.threatIntelligence.common.emptyPage.body1": "利用 Elastic 威胁情报,可以通过将多个来源的数据聚合到一个位置,轻松分析和调查潜在的安全威胁。", "xpack.threatIntelligence.common.emptyPage.body2": "您将可以查看来自所有已激活威胁情报馈送的数据,并从此页面执行操作。", "xpack.threatIntelligence.common.emptyPage.buttonText": "添加集成", @@ -31416,18 +31415,16 @@ "xpack.threatIntelligence.empty.title": "没有任何结果匹配您的搜索条件", "xpack.threatIntelligence.indicator.flyout.jsonTabLabel": "JSON", "xpack.threatIntelligence.indicator.flyout.tableTabLabel": "表", - "xpack.threatIntelligence.indicator.flyoutJson.errorMessageBody": "显示指标字段和值时出现错误。", - "xpack.threatIntelligence.indicator.flyoutJson.errorMessageTitle": "无法显示指标信息", "xpack.threatIntelligence.indicator.flyoutTable.errorMessageBody": "显示指标字段和值时出现错误。", "xpack.threatIntelligence.indicator.flyoutTable.errorMessageTitle": "无法显示指标信息", - "xpack.threatIntelligence.indicator.flyoutTable.fieldColumnLabel": "字段", - "xpack.threatIntelligence.indicator.flyoutTable.valueColumnLabel": "值", + "xpack.threatIntelligence.indicator.fieldsTable.fieldColumnLabel": "字段", + "xpack.threatIntelligence.indicator.fieldsTable.valueColumnLabel": "值", "xpack.threatIntelligence.indicator.table.actionColumnLabel": "操作", - "xpack.threatIntelligence.indicator.table.FeedColumTitle": "馈送", - "xpack.threatIntelligence.indicator.table.firstSeenColumTitle": "首次看到时间", - "xpack.threatIntelligence.indicator.table.indicatorColumTitle": "指标", - "xpack.threatIntelligence.indicator.table.indicatorTypeColumTitle": "指标类型", - "xpack.threatIntelligence.indicator.table.lastSeenColumTitle": "最后看到时间", + "xpack.threatIntelligence.field.threat.feed.name": "馈送", + "xpack.threatIntelligence.field.threat.indicator.first_seen": "首次看到时间", + "xpack.threatIntelligence.field.threat.indicator.name": "指标", + "xpack.threatIntelligence.field.threat.indicator.type": "指标类型", + "xpack.threatIntelligence.field.threat.indicator.last_seen": "最后看到时间", "xpack.threatIntelligence.indicator.table.viewDetailsButton": "查看详情", "xpack.timelines.clipboard.copy.successToastTitle": "已将字段 {field} 复制到剪贴板", "xpack.timelines.footer.autoRefreshActiveTooltip": "自动刷新已启用时,时间线将显示匹配查询的最近 {numberOfItems} 个事件。", From 0a03f8582fb8635984879fed3257ea8e04e9efa0 Mon Sep 17 00:00:00 2001 From: Klim Markelov Date: Tue, 13 Sep 2022 13:55:08 +0200 Subject: [PATCH 02/28] [Enterprise Search][Behavioural Analytics] Introduce Analytics Collection View page (#140421) * Introduce analytics collection view pages * Clean up collection events deletion functionality * Change COLLECTION_VIEW_PATH route * Apply code change requests --- ...ete_analytics_collection_api_logic.test.ts | 33 +++ .../delete_analytics_collection_api_logic.tsx | 24 +++ ...tch_analytics_collection_api_logic.test.ts | 33 +++ .../fetch_analytics_collection_api_logic.tsx | 26 +++ .../add_analytics_collection_logic.test.ts | 2 +- .../add_analytics_collection_logic.ts | 1 + .../analytics_collection_integrate.test.tsx | 35 ++++ .../analytics_collection_integrate.tsx | 121 +++++++++++ .../analytics_collection_settings.test.tsx | 51 +++++ .../analytics_collection_settings.tsx | 99 +++++++++ .../analytics_collection_view.test.tsx | 68 +++++++ .../analytics_collection_view.tsx | 189 ++++++++++++++++++ .../delete_analytics_collection_logic.test.ts | 84 ++++++++ .../delete_analytics_collection_logic.ts | 73 +++++++ .../fetch_analytics_collection_logic.test.tsx | 73 +++++++ .../fetch_analytics_collection_logic.ts | 57 ++++++ .../analytics_collection_table.test.tsx | 2 +- .../analytics_collection_table.tsx | 2 + .../public/applications/analytics/index.tsx | 6 +- .../public/applications/analytics/routes.ts | 2 +- .../delete_analytics_collection.test.ts | 18 +- .../analytics/delete_analytics_collection.ts | 13 -- 22 files changed, 978 insertions(+), 34 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/api/delete_analytics_collection/delete_analytics_collection_api_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/api/delete_analytics_collection/delete_analytics_collection_api_logic.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/api/fetch_analytics_collection/fetch_analytics_collection_api_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/api/fetch_analytics_collection/fetch_analytics_collection_api_logic.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_settings.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_settings.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/api/delete_analytics_collection/delete_analytics_collection_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/api/delete_analytics_collection/delete_analytics_collection_api_logic.test.ts new file mode 100644 index 0000000000000..1ad6c18fea3bf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/api/delete_analytics_collection/delete_analytics_collection_api_logic.test.ts @@ -0,0 +1,33 @@ +/* + * 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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { deleteAnalyticsCollection } from './delete_analytics_collection_api_logic'; + +describe('DeleteAnalyticsCollectionApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('DeleteAnalyticsCollectionsApiLogic', () => { + it('calls the analytics collections list api', async () => { + const promise = Promise.resolve(); + const name = 'collection'; + http.delete.mockReturnValue(promise); + const result = deleteAnalyticsCollection({ name }); + await nextTick(); + expect(http.delete).toHaveBeenCalledWith( + `/internal/enterprise_search/analytics/collections/${name}` + ); + await expect(result).resolves.toEqual(undefined); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/api/delete_analytics_collection/delete_analytics_collection_api_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/api/delete_analytics_collection/delete_analytics_collection_api_logic.tsx new file mode 100644 index 0000000000000..0b2f092899a6f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/api/delete_analytics_collection/delete_analytics_collection_api_logic.tsx @@ -0,0 +1,24 @@ +/* + * 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 { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export type DeleteAnalyticsCollectionApiLogicResponse = void; + +export const deleteAnalyticsCollection = async ({ name }: { name: string }) => { + const { http } = HttpLogic.values; + const route = `/internal/enterprise_search/analytics/collections/${name}`; + await http.delete(route); + + return; +}; + +export const DeleteAnalyticsCollectionAPILogic = createApiLogic( + ['analytics', 'delete_analytics_collection_api_logic'], + deleteAnalyticsCollection +); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/api/fetch_analytics_collection/fetch_analytics_collection_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/api/fetch_analytics_collection/fetch_analytics_collection_api_logic.test.ts new file mode 100644 index 0000000000000..ae21c61a8fad2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/api/fetch_analytics_collection/fetch_analytics_collection_api_logic.test.ts @@ -0,0 +1,33 @@ +/* + * 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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { fetchAnalyticsCollection } from './fetch_analytics_collection_api_logic'; + +describe('FetchAnalyticsCollectionApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('FetchAnalyticsCollectionsApiLogic', () => { + it('calls the analytics collections list api', async () => { + const promise = Promise.resolve({ name: 'result' }); + const name = 'collection'; + http.get.mockReturnValue(promise); + const result = fetchAnalyticsCollection({ name }); + await nextTick(); + expect(http.get).toHaveBeenCalledWith( + `/internal/enterprise_search/analytics/collections/${name}` + ); + await expect(result).resolves.toEqual({ name: 'result' }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/api/fetch_analytics_collection/fetch_analytics_collection_api_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/api/fetch_analytics_collection/fetch_analytics_collection_api_logic.tsx new file mode 100644 index 0000000000000..5aafc82c29e0e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/api/fetch_analytics_collection/fetch_analytics_collection_api_logic.tsx @@ -0,0 +1,26 @@ +/* + * 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 { AnalyticsCollection } from '../../../../../common/types/analytics'; + +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export type FetchAnalyticsCollectionApiLogicResponse = AnalyticsCollection; + +export const fetchAnalyticsCollection = async ({ name }: { name: string }) => { + const { http } = HttpLogic.values; + const route = `/internal/enterprise_search/analytics/collections/${name}`; + const response = await http.get(route); + + return response; +}; + +export const FetchAnalyticsCollectionAPILogic = createApiLogic( + ['analytics', 'analytics_collection_api_logic'], + fetchAnalyticsCollection +); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts index b9355574e0806..7b281208f79e8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts @@ -110,7 +110,7 @@ describe('addAnalyticsCollectionLogic', () => { expect(flashSuccessToast).toHaveBeenCalled(); jest.advanceTimersByTime(1000); await nextTick(); - expect(navigateToUrl).toHaveBeenCalledWith('/collections/test'); + expect(navigateToUrl).toHaveBeenCalledWith('/collections/test/events'); jest.useRealTimers(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.ts index a1035168d8e87..3731bb3994e9e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.ts @@ -78,6 +78,7 @@ export const AddAnalyticsCollectionLogic = kea< KibanaLogic.values.navigateToUrl( generateEncodedPath(COLLECTION_VIEW_PATH, { name, + section: 'events', }) ); }, diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.test.tsx new file mode 100644 index 0000000000000..f46454e45eb82 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.test.tsx @@ -0,0 +1,35 @@ +/* + * 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 '../../../__mocks__/shallow_useeffect.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiCodeBlock } from '@elastic/eui'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; + +import { AnalyticsCollectionIntegrate } from './analytics_collection_integrate'; + +describe('AnalyticsCollectionIntegrate', () => { + const analyticsCollections: AnalyticsCollection = { + event_retention_day_length: 180, + id: '1', + name: 'example', + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiCodeBlock)).toHaveLength(2); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx new file mode 100644 index 0000000000000..ff5cdf9c5fee1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate.tsx @@ -0,0 +1,121 @@ +/* + * 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 { + EuiCodeBlock, + EuiDescriptionList, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; +import { getEnterpriseSearchUrl } from '../../../shared/enterprise_search_url'; + +interface AnalyticsCollectionIntegrateProps { + collection: AnalyticsCollection; +} + +export const AnalyticsCollectionIntegrate: React.FC = ({ + collection, +}) => { + const analyticsDNSUrl = getEnterpriseSearchUrl(`/analytics/${collection.name}`); + const credentials = [ + { + title: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.integrateTab.credentials.collectionName', + { + defaultMessage: 'Collection name', + } + ), + description: collection.name, + }, + { + title: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.integrateTab.credentials.collectionDns', + { + defaultMessage: 'DNS URL', + } + ), + description: analyticsDNSUrl, + }, + ]; + const webclientSrc = getEnterpriseSearchUrl('/analytics.js'); + + return ( + <> + +

+ {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.integrateTab.credentials.headingTitle', + { + defaultMessage: 'Credentials', + } + )} +

+
+ + + + + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.integrateTab.embed.headingTitle', + { + defaultMessage: 'Start tracking events', + } + )} +

+
+ + +

+ {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.integrateTab.embed.description', + { + defaultMessage: + 'Embed the JS snippet below on every page of the website or application you’d like to tracks.', + } + )} +

+
+ + + {``} + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.integrateTab.scriptDescription', + { + defaultMessage: + 'Track individual events, like clicks, by calling the trackEvent method.', + } + )} +

+
+ + + {`window.elasticAnalytics.trackEvent("ResultClick", { + title: "Website Analytics", + url: "www.elasitc.co/analytics/website" +})`} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_settings.test.tsx new file mode 100644 index 0000000000000..c2d4374c5a4f6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_settings.test.tsx @@ -0,0 +1,51 @@ +/* + * 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 { setMockActions } from '../../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiButton, EuiPanel } from '@elastic/eui'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; + +import { AnalyticsCollectionSettings } from './analytics_collection_settings'; + +const mockActions = { + deleteAnalyticsCollection: jest.fn(), + setNameValue: jest.fn(), +}; + +const analyticsCollection: AnalyticsCollection = { + event_retention_day_length: 180, + id: '1', + name: 'example', +}; + +describe('AnalyticsCollectionSettings', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + setMockActions(mockActions); + + const wrapper = shallow(); + expect(wrapper.find(EuiPanel)).toHaveLength(2); + }); + + it('deletes analytics collection when delete is clicked', () => { + setMockActions(mockActions); + + const wrapper = shallow(); + + wrapper.find(EuiButton).simulate('click', { preventDefault: jest.fn() }); + expect(mockActions.deleteAnalyticsCollection).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_settings.tsx new file mode 100644 index 0000000000000..ac3a2fe490e40 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_settings.tsx @@ -0,0 +1,99 @@ +/* + * 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 { useActions, useValues } from 'kea'; + +import { + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + EuiButton, + EuiDescriptionList, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; + +import { DeleteAnalyticsCollectionLogic } from './delete_analytics_collection_logic'; + +interface AnalyticsCollectionSettingsProps { + collection: AnalyticsCollection; +} + +export const AnalyticsCollectionSettings: React.FC = ({ + collection, +}) => { + const { deleteAnalyticsCollection } = useActions(DeleteAnalyticsCollectionLogic); + const { isLoading } = useValues(DeleteAnalyticsCollectionLogic); + + return ( + <> + + + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.settingsTab.delete.headingTitle', + { + defaultMessage: 'Delete this analytics collection', + } + )} +

+
+ + +

+ {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.settingsTab.delete.warning', + { + defaultMessage: 'This action is irreversible', + } + )} +

+
+ + { + deleteAnalyticsCollection(collection.name); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.settingsTab.delete.buttonTitle', + { + defaultMessage: 'Delete this collection', + } + )} + +
+ + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.test.tsx new file mode 100644 index 0000000000000..6fc82551818ce --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.test.tsx @@ -0,0 +1,68 @@ +/* + * 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 '../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; +import { mockUseParams } from '../../../__mocks__/react_router'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; + +import { AnalyticsCollectionIntegrate } from './analytics_collection_integrate'; +import { AnalyticsCollectionSettings } from './analytics_collection_settings'; + +import { AnalyticsCollectionView } from './analytics_collection_view'; + +const mockValues = { + analyticsCollection: { + event_retention_day_length: 180, + id: '1', + name: 'Analytics Collection 1', + } as AnalyticsCollection, +}; + +const mockActions = { + fetchAnalyticsCollection: jest.fn(), +}; + +describe('AnalyticsOverview', () => { + beforeEach(() => { + jest.clearAllMocks(); + + mockUseParams.mockReturnValue({ name: '1', section: 'settings' }); + }); + + describe('empty state', () => { + it('renders when analytics collection is empty on inital query', () => { + setMockValues({ + ...mockValues, + analyticsCollection: null, + }); + setMockActions(mockActions); + const wrapper = shallow(); + + expect(mockActions.fetchAnalyticsCollection).toHaveBeenCalled(); + + expect(wrapper.find(AnalyticsCollectionSettings)).toHaveLength(0); + expect(wrapper.find(AnalyticsCollectionIntegrate)).toHaveLength(0); + }); + + it('renders with Data', async () => { + setMockValues(mockValues); + setMockActions(mockActions); + + const wrapper = shallow(); + + expect(wrapper.find(AnalyticsCollectionSettings)).toHaveLength(1); + expect(mockActions.fetchAnalyticsCollection).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx new file mode 100644 index 0000000000000..4c8e5a36e4e0f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx @@ -0,0 +1,189 @@ +/* + * 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, { useEffect } from 'react'; +import { useParams } from 'react-router-dom'; + +import { useActions, useValues } from 'kea'; + +import { + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiIcon, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { generateEncodedPath } from '../../../shared/encode_path_params'; +import { KibanaLogic } from '../../../shared/kibana'; +import { EuiButtonTo } from '../../../shared/react_router_helpers'; +import { COLLECTION_CREATION_PATH, COLLECTION_VIEW_PATH } from '../../routes'; + +import { EnterpriseSearchAnalyticsPageTemplate } from '../layout/page_template'; + +import { AnalyticsCollectionIntegrate } from './analytics_collection_integrate'; +import { AnalyticsCollectionSettings } from './analytics_collection_settings'; + +import { FetchAnalyticsCollectionLogic } from './fetch_analytics_collection_logic'; + +export const collectionViewBreadcrumbs = [ + i18n.translate('xpack.enterpriseSearch.analytics.collectionsView.breadcrumb', { + defaultMessage: 'View collection', + }), +]; + +export const AnalyticsCollectionView: React.FC = () => { + const { fetchAnalyticsCollection } = useActions(FetchAnalyticsCollectionLogic); + const { analyticsCollection, isLoading } = useValues(FetchAnalyticsCollectionLogic); + const { name, section } = useParams<{ name: string; section: string }>(); + const { navigateToUrl } = useValues(KibanaLogic); + const collectionViewTabs = [ + { + id: 'events', + label: i18n.translate('xpack.enterpriseSearch.analytics.collectionsView.tabs.eventsName', { + defaultMessage: 'Events', + }), + onClick: () => + navigateToUrl( + generateEncodedPath(COLLECTION_VIEW_PATH, { + name: analyticsCollection?.name, + section: 'events', + }) + ), + isSelected: section === 'events', + }, + { + id: 'integrate', + label: i18n.translate('xpack.enterpriseSearch.analytics.collectionsView.tabs.integrateName', { + defaultMessage: 'Integrate', + }), + prepend: , + onClick: () => + navigateToUrl( + generateEncodedPath(COLLECTION_VIEW_PATH, { + name: analyticsCollection?.name, + section: 'integrate', + }) + ), + isSelected: section === 'integrate', + }, + { + id: 'settings', + label: i18n.translate('xpack.enterpriseSearch.analytics.collectionsView.tabs.settingsName', { + defaultMessage: 'Settings', + }), + onClick: () => + navigateToUrl( + generateEncodedPath(COLLECTION_VIEW_PATH, { + name: analyticsCollection?.name, + section: 'settings', + }) + ), + isSelected: section === 'settings', + }, + ]; + + useEffect(() => { + fetchAnalyticsCollection(name); + }, []); + + return ( + + {!analyticsCollection && ( + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.headingTitle', + { + defaultMessage: 'Collections', + } + )} +

+
+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.create.buttonTitle', + { + defaultMessage: 'Create new collection', + } + )} + + +
+ )} + + + {analyticsCollection ? ( + <> + {section === 'settings' && ( + + )} + {section === 'integrate' && ( + + )} + + ) : ( + + {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.collectionNotFoundState.headingTitle', + { + defaultMessage: 'You may have deleted this analytics collection', + } + )} + + } + body={ +

+ {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.collectionNotFoundState.subHeading', + { + defaultMessage: + 'An analytics collection provides a place to store the analytics events for any given search application you are building. Create a new collection to get started.', + } + )} +

+ } + actions={[ + + {i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.create.buttonTitle', + { + defaultMessage: 'Create new collection', + } + )} + , + ]} + /> + )} +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts new file mode 100644 index 0000000000000..08ca206671de0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { + LogicMounter, + mockFlashMessageHelpers, + mockHttpValues, +} from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; +import { HttpError, Status } from '../../../../../common/types/api'; + +import { DeleteAnalyticsCollectionLogic } from './delete_analytics_collection_logic'; + +describe('deleteAnalyticsCollectionLogic', () => { + const { mount } = new LogicMounter(DeleteAnalyticsCollectionLogic); + const { http } = mockHttpValues; + + beforeEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + mount(); + }); + + const DEFAULT_VALUES = { + isLoading: true, + status: Status.IDLE, + }; + + it('has expected default values', () => { + expect(DeleteAnalyticsCollectionLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('listeners', () => { + it('calls clearFlashMessages on new makeRequest', async () => { + const promise = Promise.resolve(undefined); + http.delete.mockReturnValue(promise); + + await nextTick(); + + DeleteAnalyticsCollectionLogic.actions.makeRequest({ name: 'name' } as AnalyticsCollection); + expect(mockFlashMessageHelpers.clearFlashMessages).toHaveBeenCalledTimes(1); + }); + + it('calls flashAPIErrors on apiError', () => { + DeleteAnalyticsCollectionLogic.actions.apiError({} as HttpError); + expect(mockFlashMessageHelpers.flashAPIErrors).toHaveBeenCalledTimes(1); + expect(mockFlashMessageHelpers.flashAPIErrors).toHaveBeenCalledWith({}); + }); + + it('calls makeRequest on deleteAnalyticsCollections', async () => { + const collectionName = 'name'; + + jest.useFakeTimers(); + DeleteAnalyticsCollectionLogic.actions.makeRequest = jest.fn(); + DeleteAnalyticsCollectionLogic.actions.deleteAnalyticsCollection(collectionName); + jest.advanceTimersByTime(150); + await nextTick(); + expect(DeleteAnalyticsCollectionLogic.actions.makeRequest).toHaveBeenCalledWith({ + name: collectionName, + }); + }); + }); + + describe('selectors', () => { + describe('analyticsCollection', () => { + it('updates when apiSuccess listener triggered', () => { + DeleteAnalyticsCollectionLogic.actions.apiSuccess(); + + expect(DeleteAnalyticsCollectionLogic.values).toEqual({ + ...DEFAULT_VALUES, + isLoading: false, + status: Status.SUCCESS, + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.ts new file mode 100644 index 0000000000000..0d6bf900f528a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.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 { kea, MakeLogicType } from 'kea'; + +import { i18n } from '@kbn/i18n'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; +import { Status } from '../../../../../common/types/api'; +import { Actions } from '../../../shared/api_logic/create_api_logic'; +import { + flashAPIErrors, + clearFlashMessages, + flashSuccessToast, +} from '../../../shared/flash_messages'; +import { KibanaLogic } from '../../../shared/kibana'; +import { + DeleteAnalyticsCollectionAPILogic, + DeleteAnalyticsCollectionApiLogicResponse, +} from '../../api/delete_analytics_collection/delete_analytics_collection_api_logic'; +import { ROOT_PATH } from '../../routes'; + +export interface DeleteAnalyticsCollectionActions { + apiError: Actions<{}, DeleteAnalyticsCollectionApiLogicResponse>['apiError']; + apiSuccess: Actions<{}, DeleteAnalyticsCollectionApiLogicResponse>['apiSuccess']; + deleteAnalyticsCollection(name: string): { name: string }; + makeRequest: Actions<{}, DeleteAnalyticsCollectionApiLogicResponse>['makeRequest']; +} +export interface DeleteAnalyticsCollectionValues { + analyticsCollection: AnalyticsCollection; + isLoading: boolean; + status: Status; +} + +export const DeleteAnalyticsCollectionLogic = kea< + MakeLogicType +>({ + actions: { + deleteAnalyticsCollection: (name) => ({ name }), + }, + connect: { + actions: [DeleteAnalyticsCollectionAPILogic, ['makeRequest', 'apiSuccess', 'apiError']], + values: [DeleteAnalyticsCollectionAPILogic, ['status']], + }, + listeners: ({ actions }) => ({ + apiError: (e) => flashAPIErrors(e), + apiSuccess: async (undefined, breakpoint) => { + flashSuccessToast( + i18n.translate('xpack.enterpriseSearch.analytics.collectionsDelete.action.successMessage', { + defaultMessage: 'The collection has been successfully deleted', + }) + ); + // Wait for propagation of the collection deletion + await breakpoint(1000); + KibanaLogic.values.navigateToUrl(ROOT_PATH); + }, + deleteAnalyticsCollection: ({ name }) => { + actions.makeRequest({ name }); + }, + makeRequest: () => clearFlashMessages(), + }), + path: ['enterprise_search', 'analytics', 'collections', 'delete'], + selectors: ({ selectors }) => ({ + isLoading: [ + () => [selectors.status], + (status) => [Status.LOADING, Status.IDLE].includes(status), + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.test.tsx new file mode 100644 index 0000000000000..3e56bd816b0a8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.test.tsx @@ -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 { LogicMounter, mockFlashMessageHelpers } from '../../../__mocks__/kea_logic'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; +import { HttpError, Status } from '../../../../../common/types/api'; + +import { FetchAnalyticsCollectionLogic } from './fetch_analytics_collection_logic'; + +describe('fetchAnalyticsCollectionLogic', () => { + const { mount } = new LogicMounter(FetchAnalyticsCollectionLogic); + + beforeEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + mount(); + }); + + const DEFAULT_VALUES = { + analyticsCollection: null, + data: undefined, + isLoading: true, + status: Status.IDLE, + }; + + it('has expected default values', () => { + expect(FetchAnalyticsCollectionLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('listeners', () => { + it('calls clearFlashMessages on new makeRequest', () => { + FetchAnalyticsCollectionLogic.actions.makeRequest({} as AnalyticsCollection); + expect(mockFlashMessageHelpers.clearFlashMessages).toHaveBeenCalledTimes(1); + }); + + it('calls flashAPIErrors on apiError', () => { + FetchAnalyticsCollectionLogic.actions.apiError({} as HttpError); + expect(mockFlashMessageHelpers.flashAPIErrors).toHaveBeenCalledTimes(1); + expect(mockFlashMessageHelpers.flashAPIErrors).toHaveBeenCalledWith({}); + }); + + it('calls makeRequest on fetchAnalyticsCollections', async () => { + const name = 'name'; + + FetchAnalyticsCollectionLogic.actions.makeRequest = jest.fn(); + FetchAnalyticsCollectionLogic.actions.fetchAnalyticsCollection(name); + expect(FetchAnalyticsCollectionLogic.actions.makeRequest).toHaveBeenCalledWith({ + name, + }); + }); + }); + + describe('selectors', () => { + describe('analyticsCollections', () => { + it('updates when apiSuccess listener triggered', () => { + FetchAnalyticsCollectionLogic.actions.apiSuccess({} as AnalyticsCollection); + + expect(FetchAnalyticsCollectionLogic.values).toEqual({ + ...DEFAULT_VALUES, + analyticsCollection: {}, + data: {}, + isLoading: false, + status: Status.SUCCESS, + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.ts new file mode 100644 index 0000000000000..819936ff53904 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.ts @@ -0,0 +1,57 @@ +/* + * 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 { kea, MakeLogicType } from 'kea'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; +import { Status } from '../../../../../common/types/api'; +import { Actions } from '../../../shared/api_logic/create_api_logic'; +import { flashAPIErrors, clearFlashMessages } from '../../../shared/flash_messages'; +import { + FetchAnalyticsCollectionAPILogic, + FetchAnalyticsCollectionApiLogicResponse, +} from '../../api/fetch_analytics_collection/fetch_analytics_collection_api_logic'; + +export interface FetchAnalyticsCollectionActions { + apiError: Actions<{}, FetchAnalyticsCollectionApiLogicResponse>['apiError']; + apiSuccess: Actions<{}, FetchAnalyticsCollectionApiLogicResponse>['apiSuccess']; + fetchAnalyticsCollection(name: string): AnalyticsCollection; + makeRequest: Actions<{}, FetchAnalyticsCollectionApiLogicResponse>['makeRequest']; +} +export interface FetchAnalyticsCollectionValues { + analyticsCollection: AnalyticsCollection; + data: typeof FetchAnalyticsCollectionAPILogic.values.data; + isLoading: boolean; + status: Status; +} + +export const FetchAnalyticsCollectionLogic = kea< + MakeLogicType +>({ + actions: { + fetchAnalyticsCollection: (name) => ({ name }), + }, + connect: { + actions: [FetchAnalyticsCollectionAPILogic, ['makeRequest', 'apiSuccess', 'apiError']], + values: [FetchAnalyticsCollectionAPILogic, ['data', 'status']], + }, + listeners: ({ actions }) => ({ + apiError: (e) => flashAPIErrors(e), + fetchAnalyticsCollection: ({ name }) => { + actions.makeRequest({ name }); + }, + makeRequest: () => clearFlashMessages(), + }), + path: ['enterprise_search', 'analytics', 'collection'], + selectors: ({ selectors }) => ({ + analyticsCollection: [() => [selectors.data], (data) => data || null], + isLoading: [ + () => [selectors.status], + (status) => [Status.LOADING, Status.IDLE].includes(status), + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.test.tsx index bb5e2bfb324c2..8ca63956f6b79 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.test.tsx @@ -39,6 +39,6 @@ describe('AnalyticsCollectionTable', () => { expect(rows).toHaveLength(1); expect(rows[0]).toMatchObject(analyticsCollections[0]); - expect(wrapper.dive().find(EuiLinkTo).first().prop('to')).toBe('/collections/example'); + expect(wrapper.dive().find(EuiLinkTo).first().prop('to')).toBe('/collections/example/events'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.tsx index 6ce6e677b5a26..6a983fbd5587f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.tsx @@ -39,6 +39,7 @@ export const AnalyticsCollectionTable: React.FC = {name} @@ -57,6 +58,7 @@ export const AnalyticsCollectionTable: React.FC = navigateToUrl( generateEncodedPath(COLLECTION_VIEW_PATH, { name: collection.name, + section: 'events', }) ), type: 'icon', diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx index 5b41c1e1a653c..b2f7d20bf261c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx @@ -14,9 +14,10 @@ import { VersionMismatchPage } from '../shared/version_mismatch'; import { AddAnalyticsCollection } from './components/add_analytics_collections/add_analytics_collection'; +import { AnalyticsCollectionView } from './components/analytics_collection_view/analytics_collection_view'; import { AnalyticsOverview } from './components/analytics_overview/analytics_overview'; -import { ROOT_PATH, COLLECTION_CREATION_PATH } from './routes'; +import { ROOT_PATH, COLLECTION_CREATION_PATH, COLLECTION_VIEW_PATH } from './routes'; export const Analytics: React.FC = (props) => { const { enterpriseSearchVersion, kibanaVersion } = props; @@ -37,6 +38,9 @@ export const Analytics: React.FC = (props) => { + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/routes.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/routes.ts index 66803c3e87b10..14532ecfd5079 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/routes.ts @@ -8,4 +8,4 @@ export const ROOT_PATH = '/'; export const COLLECTIONS_PATH = '/collections'; export const COLLECTION_CREATION_PATH = `${COLLECTIONS_PATH}/new`; -export const COLLECTION_VIEW_PATH = `${COLLECTIONS_PATH}/:name`; +export const COLLECTION_VIEW_PATH = `${COLLECTIONS_PATH}/:name/:section`; diff --git a/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.test.ts b/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.test.ts index dbb9585dbe27c..a321a0d118cf4 100644 --- a/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.test.ts @@ -40,18 +40,7 @@ describe('delete analytics collection lib function', () => { }); describe('deleting analytics collections', () => { - it('should delete an analytics collection and its events indices', async () => { - const indices = [ - { - name: 'elastic_analytics-events-my-collection-12.12.12', - }, - { - name: 'elastic_analytics-events-my-collection-13.12.12', - }, - ]; - (fetchIndices as jest.Mock).mockImplementationOnce(() => { - return Promise.resolve(indices); - }); + it('should delete an analytics collection', async () => { (fetchAnalyticsCollectionByName as jest.Mock).mockImplementationOnce(() => { return Promise.resolve({ event_retention_day_length: 180, @@ -68,11 +57,6 @@ describe('delete analytics collection lib function', () => { id: 'example-id', index: ANALYTICS_COLLECTIONS_INDEX, }); - - expect(mockClient.asCurrentUser.indices.delete).toHaveBeenCalledWith({ - ignore_unavailable: true, - index: indices.map((index) => index.name), - }); }); it('should throw an exception when analytics collection does not exist', async () => { diff --git a/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.ts b/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.ts index 687296a27c13f..d07a271bfc01c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.ts +++ b/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.ts @@ -10,20 +10,9 @@ import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { ANALYTICS_COLLECTIONS_INDEX } from '../..'; import { ErrorCode } from '../../../common/types/error_codes'; -import { fetchIndices } from '../indices/fetch_indices'; import { fetchAnalyticsCollectionByName } from './fetch_analytics_collection'; -const deleteAnalyticsCollectionEvents = async (client: IScopedClusterClient, name: string) => { - const indexPattern = `elastic_analytics-events-${name}-*`; - const indices = await fetchIndices(client, indexPattern, true, false); - - await client.asCurrentUser.indices.delete({ - ignore_unavailable: true, - index: indices.map((index) => index.name), - }); -}; - export const deleteAnalyticsCollectionByName = async ( client: IScopedClusterClient, name: string @@ -34,8 +23,6 @@ export const deleteAnalyticsCollectionByName = async ( throw new Error(ErrorCode.ANALYTICS_COLLECTION_NOT_FOUND); } - await deleteAnalyticsCollectionEvents(client, name); - await client.asCurrentUser.delete({ id: analyticsCollection.id, index: ANALYTICS_COLLECTIONS_INDEX, From 2808ecadc9c2f94d3e0d029730296ec94557a7b8 Mon Sep 17 00:00:00 2001 From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com> Date: Tue, 13 Sep 2022 17:31:01 +0500 Subject: [PATCH 03/28] [Console] Add missing autocompletes (#140275) * [Console] Add missing autocompletes * Add script key for moving function aggregation * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: Muhammad Ibragimov Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../lib/spec_definitions/js/aggregations.ts | 6 ++- .../server/lib/spec_definitions/js/aliases.ts | 2 + .../overrides/indices.put_index_template.json | 31 ++++++++++++++++ .../json/overrides/slm.put_lifecycle.json | 37 ++++++++++++++++++- 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/plugins/console/server/lib/spec_definitions/json/overrides/indices.put_index_template.json diff --git a/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts b/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts index 9403d6d56e486..16e7a77366c7d 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts @@ -301,6 +301,9 @@ const rules = { format: 'yyyy-MM-dd', time_zone: '00:00', missing: '', + calendar_interval: { + __one_of: ['year', 'quarter', 'week', 'day', 'hour', 'minute', 'second'], + }, }, geo_distance: { __template: { @@ -473,7 +476,7 @@ const rules = { percents: [], }, sum_bucket: simple_pipeline, - moving_avg: { + moving_fn: { __template: { buckets_path: '', }, @@ -489,6 +492,7 @@ const rules = { gamma: 0.5, period: 7, }, + script: '', }, cumulative_sum: { __template: { diff --git a/src/plugins/console/server/lib/spec_definitions/js/aliases.ts b/src/plugins/console/server/lib/spec_definitions/js/aliases.ts index 9eb7554fabeba..ef4a30c1aa012 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/aliases.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/aliases.ts @@ -14,6 +14,8 @@ export const aliases = (specService: SpecDefinitionsService) => { routing: '1', search_routing: '1,2', index_routing: '1', + is_write_index: false, + is_hidden: false, }; specService.addGlobalAutocompleteRules('aliases', { '*': aliasRules, diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/indices.put_index_template.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/indices.put_index_template.json new file mode 100644 index 0000000000000..95d3eabc97c36 --- /dev/null +++ b/src/plugins/console/server/lib/spec_definitions/json/overrides/indices.put_index_template.json @@ -0,0 +1,31 @@ +{ + "indices.put_index_template": { + "data_autocomplete_rules": { + "composed_of": [], + "index_patterns": [], + "data_stream": { + "__template": { + "allow_custom_routing": false, + "hidden": false, + "index_mode": "" + } + }, + "template": { + "settings": { + "__scope_link": "put_settings" + }, + "aliases": { + "__template": { + "NAME": {} + } + }, + "mappings": { + "__scope_link": "put_mapping" + } + }, + "_meta": {}, + "priority": 0, + "version": 0 + } + } +} diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/slm.put_lifecycle.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/slm.put_lifecycle.json index f14fa5a4ad634..ae653df104b51 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/overrides/slm.put_lifecycle.json +++ b/src/plugins/console/server/lib/spec_definitions/json/overrides/slm.put_lifecycle.json @@ -4,7 +4,42 @@ "schedule": "", "name": "", "repository": "", - "config": {} + "config": { + "expanded_wildcards": { + "__one_of": [ + "open", + "closed", + "hidden", + "none", + "all" + ] + }, + "ignore_unavailable": false, + "include_global_state": false, + "indices": [ + "" + ], + "feature_states": [ + "" + ], + "partial": false, + "metadata": {} + }, + "retention": { + "expire_after": { + "__one_of": [ + "d", + "h", + "m", + "s", + "ms", + "micros", + "nanos" + ] + }, + "min_count": 0, + "max_count": 0 + } } } } From 8e5ed87f18e1a6c7b43644a9cb024a146b2a6420 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 13 Sep 2022 14:37:09 +0200 Subject: [PATCH 04/28] [Mappings Editor] allow time series related properties (#140278) --- .../constants/parameters_definition.tsx | 14 ++++++++++++++ .../mappings_editor/types/document_fields.ts | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx index ae5915a032b8e..c00885de6b967 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx @@ -1078,4 +1078,18 @@ export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinitio }, schema: t.union([t.literal(2), t.literal(3), t.literal(4)]), }, + time_series_metric: { + fieldConfig: { + defaultValue: null, + type: FIELD_TYPES.SELECT, + }, + schema: t.union([t.literal('gauge'), t.literal('counter'), t.null]), + }, + time_series_dimension: { + fieldConfig: { + type: FIELD_TYPES.CHECKBOX, + defaultValue: false, + }, + schema: t.boolean, + }, }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts index 408339f5a10a6..6e50cdfe3ba9a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts @@ -156,7 +156,9 @@ export type ParameterName = | 'relations' | 'max_shingle_size' | 'value' - | 'meta'; + | 'meta' + | 'time_series_metric' + | 'time_series_dimension'; export interface Parameter { fieldConfig: FieldConfig; From 678edce2d81ae84327fb82291a6f51d9026b7cc7 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 13 Sep 2022 14:41:40 +0200 Subject: [PATCH 05/28] Enable TSDB downsampling ILM configuration (#138748) --- .../downsample_interval.test.ts | 130 ++++++++++++++++++ .../policy_serialization.test.ts | 9 ++ .../helpers/actions/downsample_actions.ts | 44 ++++++ .../helpers/actions/index.ts | 1 + .../helpers/actions/phases.ts | 4 + .../common/types/policies.ts | 9 ++ .../phases/cold_phase/cold_phase.tsx | 3 + .../components/phases/hot_phase/hot_phase.tsx | 2 + .../phases/shared_fields/downsample_field.tsx | 82 +++++++++++ .../components/phases/shared_fields/index.ts | 2 + .../searchable_snapshot_field.tsx | 2 +- .../phases/warm_phase/warm_phase.tsx | 3 + .../sections/edit_policy/constants.ts | 39 ++++++ .../sections/edit_policy/form/deserializer.ts | 33 +++++ .../sections/edit_policy/form/schema.ts | 64 ++++++++- .../edit_policy/form/serializer/serializer.ts | 39 ++++++ .../sections/edit_policy/form/validations.ts | 103 +++++++++++++- .../sections/edit_policy/i18n_texts.ts | 15 ++ .../application/sections/edit_policy/types.ts | 15 +- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 22 files changed, 590 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/downsample_interval.test.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/downsample_actions.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/downsample_field.tsx diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/downsample_interval.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/downsample_interval.test.ts new file mode 100644 index 0000000000000..b24a6d1a32cb7 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/downsample_interval.test.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 { act } from 'react-dom/test-utils'; +import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; + +import { PhaseWithDownsample } from '../../../../common/types'; +import { setupEnvironment } from '../../helpers'; +import { setupValidationTestBed, ValidationTestBed } from './validation.helpers'; + +describe(' downsample interval validation', () => { + let testBed: ValidationTestBed; + let actions: ValidationTestBed['actions']; + const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setDefaultResponses(); + httpRequestsMockHelpers.setLoadPolicies([]); + + await act(async () => { + testBed = await setupValidationTestBed(httpSetup); + }); + + const { component } = testBed; + component.update(); + ({ actions } = testBed); + await actions.setPolicyName('mypolicy'); + }); + + [ + { + name: `doesn't allow empty interval`, + value: '', + error: [i18nTexts.editPolicy.errors.numberRequired], + }, + { + name: `doesn't allow 0 for interval`, + value: '0', + error: [i18nTexts.editPolicy.errors.numberGreatThan0Required], + }, + { + name: `doesn't allow -1 for interval`, + value: '-1', + error: [i18nTexts.editPolicy.errors.numberGreatThan0Required], + }, + { + name: `doesn't allow decimals for timing (with dot)`, + value: '5.5', + error: [i18nTexts.editPolicy.errors.integerRequired], + }, + { + name: `doesn't allow decimals for timing (with comma)`, + value: '5,5', + error: [i18nTexts.editPolicy.errors.integerRequired], + }, + ].forEach((testConfig: { name: string; value: string; error: string[] }) => { + (['hot', 'warm', 'cold'] as PhaseWithDownsample[]).forEach((phase: PhaseWithDownsample) => { + const { name, value, error } = testConfig; + test(`${phase}: ${name}`, async () => { + if (phase !== 'hot') { + await actions.togglePhase(phase); + } + + await actions[phase].downsample.toggle(); + + // 1. We first set as dummy value to have a starting min_age value + await actions[phase].downsample.setDownsampleInterval('111'); + // 2. At this point we are sure there will be a change of value and that any validation + // will be displayed under the field. + await actions[phase].downsample.setDownsampleInterval(value); + + actions.errors.waitForValidation(); + + actions.errors.expectMessages(error); + }); + }); + }); + + test('should validate an interval is greater or multiple than previous phase interval', async () => { + await actions.togglePhase('warm'); + await actions.togglePhase('cold'); + + await actions.hot.downsample.toggle(); + await actions.hot.downsample.setDownsampleInterval('60', 'm'); + + await actions.warm.downsample.toggle(); + await actions.warm.downsample.setDownsampleInterval('1', 'h'); + + actions.errors.waitForValidation(); + actions.errors.expectMessages( + ['Must be greater than and a multiple of the hot phase value (60m)'], + 'warm' + ); + + await actions.cold.downsample.toggle(); + await actions.cold.downsample.setDownsampleInterval('90', 'm'); + actions.errors.waitForValidation(); + actions.errors.expectMessages( + ['Must be greater than and a multiple of the warm phase value (1h)'], + 'cold' + ); + + // disable warm phase; + await actions.togglePhase('warm'); + // TODO: there is a bug that disabling a phase doesn't trigger downsample validation in other phases, + // users can work around it by changing the value + await actions.cold.downsample.setDownsampleInterval('120', 'm'); + actions.errors.waitForValidation(); + actions.errors.expectMessages([], 'cold'); + + await actions.cold.downsample.setDownsampleInterval('90', 'm'); + actions.errors.waitForValidation(); + actions.errors.expectMessages( + ['Must be greater than and a multiple of the hot phase value (60m)'], + 'cold' + ); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts index 14f1403ad536d..321a7efbfc5e3 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts @@ -201,6 +201,8 @@ describe(' serialization', () => { await actions.hot.setShrinkCount('2'); await actions.hot.toggleReadonly(); await actions.hot.setIndexPriority('123'); + await actions.hot.downsample.toggle(); + await actions.hot.downsample.setDownsampleInterval('2', 'h'); await actions.savePolicy(); @@ -231,6 +233,7 @@ describe(' serialization', () => { priority: 123, }, readonly: {}, + downsample: { fixed_interval: '2h' }, }, }, }, @@ -323,6 +326,8 @@ describe(' serialization', () => { await actions.warm.setBestCompression(true); await actions.warm.toggleReadonly(); await actions.warm.setIndexPriority('123'); + await actions.warm.downsample.toggle(); + await actions.warm.downsample.setDownsampleInterval('20', 'm'); await actions.savePolicy(); expect(httpSetup.post).toHaveBeenLastCalledWith( @@ -360,6 +365,7 @@ describe(' serialization', () => { number_of_replicas: 123, }, readonly: {}, + downsample: { fixed_interval: '20m' }, }, }, }, @@ -463,6 +469,8 @@ describe(' serialization', () => { await actions.cold.setReplicas('123'); await actions.cold.toggleReadonly(); await actions.cold.setIndexPriority('123'); + await actions.cold.downsample.toggle(); + await actions.cold.downsample.setDownsampleInterval('5'); await actions.savePolicy(); @@ -494,6 +502,7 @@ describe(' serialization', () => { number_of_replicas: 123, }, readonly: {}, + downsample: { fixed_interval: '5d' }, }, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/downsample_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/downsample_actions.ts new file mode 100644 index 0000000000000..315ed3d58520a --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/downsample_actions.ts @@ -0,0 +1,44 @@ +/* + * 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 { TestBed } from '@kbn/test-jest-helpers'; +import { act } from 'react-dom/test-utils'; +import { Phase } from '../../../../common/types'; +import { createFormToggleAction } from '..'; + +const createSetDownsampleIntervalAction = + (testBed: TestBed, phase: Phase) => async (value: string, units?: string) => { + const { find, component } = testBed; + + await act(async () => { + find(`${phase}-downsampleFixedInterval`).simulate('change', { target: { value } }); + }); + component.update(); + + if (units) { + act(() => { + find(`${phase}-downsampleFixedIntervalUnits.show-filters-button`).simulate('click'); + }); + component.update(); + + act(() => { + find(`${phase}-downsampleFixedIntervalUnits.filter-option-${units}`).simulate('click'); + }); + component.update(); + } + }; + +export const createDownsampleActions = (testBed: TestBed, phase: Phase) => { + const { exists } = testBed; + return { + downsample: { + exists: () => exists(`${phase}-downsampleSwitch`), + toggle: createFormToggleAction(testBed, `${phase}-downsampleSwitch`), + setDownsampleInterval: createSetDownsampleIntervalAction(testBed, phase), + }, + }; +}; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index.ts index f2579031dbad9..fefbd0363d449 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index.ts @@ -21,6 +21,7 @@ export { createForceMergeActions } from './forcemerge_actions'; export { createReadonlyActions } from './readonly_actions'; export { createIndexPriorityActions } from './index_priority_actions'; export { createShrinkActions } from './shrink_actions'; +export { createDownsampleActions } from './downsample_actions'; export { createHotPhaseActions, createWarmPhaseActions, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts index d317b09d4d305..ffe3aad05b0eb 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts @@ -16,6 +16,7 @@ import { createNodeAllocationActions, createReplicasAction, createSnapshotPolicyActions, + createDownsampleActions, } from '.'; export const createHotPhaseActions = (testBed: TestBed) => { @@ -26,6 +27,7 @@ export const createHotPhaseActions = (testBed: TestBed) => { ...createReadonlyActions(testBed, 'hot'), ...createIndexPriorityActions(testBed, 'hot'), ...createSearchableSnapshotActions(testBed, 'hot'), + ...createDownsampleActions(testBed, 'hot'), }, }; }; @@ -39,6 +41,7 @@ export const createWarmPhaseActions = (testBed: TestBed) => { ...createIndexPriorityActions(testBed, 'warm'), ...createNodeAllocationActions(testBed, 'warm'), ...createReplicasAction(testBed, 'warm'), + ...createDownsampleActions(testBed, 'warm'), }, }; }; @@ -51,6 +54,7 @@ export const createColdPhaseActions = (testBed: TestBed) => { ...createIndexPriorityActions(testBed, 'cold'), ...createNodeAllocationActions(testBed, 'cold'), ...createSearchableSnapshotActions(testBed, 'cold'), + ...createDownsampleActions(testBed, 'cold'), }, }; }; diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index 6c5a9b7c36c50..fcc9e89da4796 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -15,6 +15,8 @@ export type PhaseWithTiming = keyof Omit; export type PhaseExceptDelete = keyof Omit; +export type PhaseWithDownsample = 'hot' | 'warm' | 'cold'; + export interface SerializedPolicy { name: string; phases: Phases; @@ -93,6 +95,7 @@ export interface SerializedHotPhase extends SerializedPhase { forcemerge?: ForcemergeAction; readonly?: {}; shrink?: ShrinkAction; + downsample?: DownsampleAction; set_priority?: { priority: number | null; @@ -110,6 +113,7 @@ export interface SerializedWarmPhase extends SerializedPhase { shrink?: ShrinkAction; forcemerge?: ForcemergeAction; readonly?: {}; + downsample?: DownsampleAction; set_priority?: { priority: number | null; }; @@ -121,6 +125,7 @@ export interface SerializedColdPhase extends SerializedPhase { actions: { freeze?: {}; readonly?: {}; + downsample?: DownsampleAction; allocate?: AllocateAction; set_priority?: { priority: number | null; @@ -178,6 +183,10 @@ export interface ForcemergeAction { index_codec?: 'best_compression'; } +export interface DownsampleAction { + fixed_interval: string; +} + export interface LegacyPolicy { name: string; phases: { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx index 58f8544174044..e3214eab3ec47 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx @@ -15,6 +15,7 @@ import { IndexPriorityField, ReplicasField, ReadonlyField, + DownsampleField, } from '../shared_fields'; import { Phase } from '../phase'; @@ -38,6 +39,8 @@ export const ColdPhase: FunctionComponent = () => { {/* Readonly section */} {!isUsingSearchableSnapshotInHotPhase && } + {!isUsingSearchableSnapshotInHotPhase && } + {/* Data tier allocation section */} { {license.canUseSearchableSnapshot() && } + )} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/downsample_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/downsample_field.tsx new file mode 100644 index 0000000000000..e25068be8b13a --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/downsample_field.tsx @@ -0,0 +1,82 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n-react'; +import { EuiTextColor } from '@elastic/eui'; +import { UnitField } from './unit_field'; +import { fixedIntervalUnits } from '../../../constants'; +import { UseField } from '../../../form'; +import { NumericField } from '../../../../../../shared_imports'; +import { ToggleFieldWithDescribedFormRow } from '../../described_form_row'; +// import { LearnMoreLink } from '../../learn_more_link'; +import { i18nTexts } from '../../../i18n_texts'; +import { PhaseWithDownsample } from '../../../../../../../common/types'; + +interface Props { + phase: PhaseWithDownsample; +} + +export const DownsampleField: React.FunctionComponent = ({ phase }) => { + // const { docLinks } = useKibana().services; + + const downsampleEnabledPath = `_meta.${phase}.downsample.enabled`; + const downsampleIntervalSizePath = `_meta.${phase}.downsample.fixedIntervalSize`; + const downsampleIntervalUnitsPath = `_meta.${phase}.downsample.fixedIntervalUnits`; + + return ( + + + + } + description={ + + {' '} + {/* TODO: add when available*/} + {/* */} + + } + fullWidth + titleSize="xs" + switchProps={{ + 'data-test-subj': `${phase}-downsampleSwitch`, + path: downsampleEnabledPath, + }} + > + + ), + }, + }} + /> + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts index 220f0bd8e941a..34f4d09877e3a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts @@ -22,3 +22,5 @@ export { ReadonlyField } from './readonly_field'; export { ReplicasField } from './replicas_field'; export { IndexPriorityField } from './index_priority_field'; + +export { DownsampleField } from './downsample_field'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx index fbf5dc5c5af4b..f36067923e1b7 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx @@ -265,7 +265,7 @@ export const SearchableSnapshotField: FunctionComponent = ({ 'xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotCalloutBody', { defaultMessage: - 'Force merge, shrink and read only actions are not allowed when converting data to a fully-mounted index in this phase.', + 'Force merge, shrink, downsample and read only actions are not allowed when converting data to a fully-mounted index in this phase.', } )} data-test-subj="searchableSnapshotFieldsDisabledCallout" diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index 29445ac8e4715..fba9556b5f4ea 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -17,6 +17,7 @@ import { ShrinkField, ReadonlyField, ReplicasField, + DownsampleField, } from '../shared_fields'; import { Phase } from '../phase'; @@ -42,6 +43,8 @@ export const WarmPhase: FunctionComponent = () => { {!isUsingSearchableSnapshotInHotPhase && } + {!isUsingSearchableSnapshotInHotPhase && } + {/* Data tier allocation section */} { @@ -125,6 +142,14 @@ export const createDeserializer = draft.phases.warm.actions.shrink.max_primary_shard_size = primaryShardSize.size; draft._meta.warm.shrink.maxPrimaryShardSizeUnits = primaryShardSize.units; } + + if (draft.phases.warm?.actions.downsample?.fixed_interval) { + const downsampleInterval = splitSizeAndUnits( + draft.phases.warm.actions.downsample.fixed_interval + ); + draft._meta.warm.downsample.fixedIntervalUnits = downsampleInterval.units; + draft._meta.warm.downsample.fixedIntervalSize = downsampleInterval.size; + } } if (draft.phases.cold) { @@ -139,6 +164,14 @@ export const createDeserializer = draft.phases.cold.min_age = minAge.size; draft._meta.cold.minAgeUnit = minAge.units; } + + if (draft.phases.cold?.actions.downsample?.fixed_interval) { + const downsampleInterval = splitSizeAndUnits( + draft.phases.cold.actions.downsample.fixed_interval + ); + draft._meta.cold.downsample.fixedIntervalUnits = downsampleInterval.units; + draft._meta.cold.downsample.fixedIntervalSize = downsampleInterval.size; + } } if (draft.phases.frozen) { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index 0e9f4bd953c2a..2299cd2fed344 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts @@ -7,17 +7,22 @@ import { i18n } from '@kbn/i18n'; -import { PhaseExceptDelete, PhaseWithTiming } from '../../../../../common/types'; -import { FormSchema, fieldValidators } from '../../../../shared_imports'; +import { + PhaseExceptDelete, + PhaseWithDownsample, + PhaseWithTiming, +} from '../../../../../common/types'; +import { fieldValidators, FormSchema } from '../../../../shared_imports'; import { defaultIndexPriority } from '../../../constants'; -import { ROLLOVER_FORM_PATHS, CLOUD_DEFAULT_REPO } from '../constants'; +import { CLOUD_DEFAULT_REPO, ROLLOVER_FORM_PATHS } from '../constants'; import { i18nTexts } from '../i18n_texts'; import { ifExistsNumberGreaterThanZero, ifExistsNumberNonNegative, - rolloverThresholdsValidator, integerValidator, minAgeGreaterThanPreviousPhase, + rolloverThresholdsValidator, + downsampleIntervalMultipleOfPreviousOne, } from './validations'; const rolloverFormPaths = Object.values(ROLLOVER_FORM_PATHS); @@ -156,6 +161,54 @@ const getMinAgeField = (phase: PhaseWithTiming, defaultValue?: string) => ({ ], }); +const getDownsampleFieldsToValidateOnChange = ( + p: PhaseWithDownsample, + includeCurrentPhase = true +) => { + const allPhases: PhaseWithDownsample[] = ['hot', 'warm', 'cold']; + const getIntervalSizePath = (currentPhase: PhaseWithDownsample) => + `_meta.${currentPhase}.downsample.fixedIntervalSize`; + const omitPreviousPhases = (currentPhase: PhaseWithDownsample) => + allPhases.slice(allPhases.indexOf(currentPhase) + (includeCurrentPhase ? 0 : 1)); + // when a phase is validated, need to also validate all downsample intervals in the next phases + return omitPreviousPhases(p).map(getIntervalSizePath); +}; +const getDownsampleSchema = (phase: PhaseWithDownsample): FormSchema['downsample'] => { + return { + enabled: { + defaultValue: false, + label: i18nTexts.editPolicy.downsampleEnabledFieldLabel, + fieldsToValidateOnChange: getDownsampleFieldsToValidateOnChange( + phase, + /* don't trigger validation on the current validation to prevent showing error state on pristine input */ + false + ), + }, + fixedIntervalSize: { + label: i18nTexts.editPolicy.downsampleIntervalFieldLabel, + fieldsToValidateOnChange: getDownsampleFieldsToValidateOnChange(phase), + validations: [ + { + validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), + }, + { + validator: ifExistsNumberGreaterThanZero, + }, + { + validator: integerValidator, + }, + { + validator: downsampleIntervalMultipleOfPreviousOne(phase), + }, + ], + }, + fixedIntervalUnits: { + defaultValue: 'd', + fieldsToValidateOnChange: getDownsampleFieldsToValidateOnChange(phase), + }, + }; +}; + export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ _meta: { hot: { @@ -197,6 +250,7 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ defaultValue: 'gb', }, }, + downsample: getDownsampleSchema('hot'), }, warm: { enabled: { @@ -239,6 +293,7 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ defaultValue: 'gb', }, }, + downsample: getDownsampleSchema('warm'), }, cold: { enabled: { @@ -269,6 +324,7 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ allocationNodeAttribute: { label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel, }, + downsample: getDownsampleSchema('cold'), }, frozen: { enabled: { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts index 75fb48a5becd3..c0ae46bf18c4f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts @@ -124,6 +124,19 @@ export const createSerializer = } else { delete hotPhaseActions.shrink!.max_primary_shard_size; } + + /** + * HOT PHASE DOWNSAMPLE + */ + if (_meta.hot?.downsample?.enabled) { + hotPhaseActions.downsample = { + ...hotPhaseActions.downsample, + fixed_interval: `${_meta.hot.downsample.fixedIntervalSize!}${_meta.hot.downsample + .fixedIntervalUnits!}`, + }; + } else { + delete hotPhaseActions.downsample; + } } else { delete hotPhaseActions.rollover; delete hotPhaseActions.forcemerge; @@ -214,6 +227,19 @@ export const createSerializer = } else { delete warmPhase.actions.shrink!.max_primary_shard_size; } + + /** + * WARM PHASE DOWNSAMPLE + */ + if (_meta.warm?.downsample?.enabled) { + warmPhase.actions.downsample = { + ...warmPhase.actions.downsample, + fixed_interval: `${_meta.warm.downsample.fixedIntervalSize!}${_meta.warm.downsample + .fixedIntervalUnits!}`, + }; + } else { + delete warmPhase.actions.downsample; + } } else { delete draft.phases.warm; } @@ -278,6 +304,19 @@ export const createSerializer = } else { delete coldPhase.actions.searchable_snapshot; } + + /** + * COLD PHASE DOWNSAMPLE + */ + if (_meta.cold?.downsample?.enabled) { + coldPhase.actions.downsample = { + ...coldPhase.actions.downsample, + fixed_interval: `${_meta.cold.downsample.fixedIntervalSize!}${_meta.cold.downsample + .fixedIntervalUnits!}`, + }; + } else { + delete coldPhase.actions.downsample; + } } else { delete draft.phases.cold; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts index 04f59707ea634..5035071a1f2a1 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import moment from 'moment'; import { fieldValidators, ValidationFunc, @@ -16,7 +17,7 @@ import { import { ROLLOVER_FORM_PATHS } from '../constants'; import { i18nTexts } from '../i18n_texts'; -import { PhaseWithTiming, PolicyFromES } from '../../../../../common/types'; +import { PhaseWithDownsample, PhaseWithTiming, PolicyFromES } from '../../../../../common/types'; import { FormInternal } from '../types'; const { numberGreaterThanField, containsCharsField, emptyField, startsWithField } = fieldValidators; @@ -272,3 +273,103 @@ export const minAgeGreaterThanPreviousPhase = } } }; + +export const downsampleIntervalMultipleOfPreviousOne = + (phase: PhaseWithDownsample) => + ({ formData }: { formData: Record }) => { + if (phase === 'hot') return; + + const getValueFor = (_phase: PhaseWithDownsample) => { + const intervalSize = formData[`_meta.${_phase}.downsample.fixedIntervalSize`]; + const intervalUnits = formData[`_meta.${_phase}.downsample.fixedIntervalUnits`]; + + if (!intervalSize || !intervalUnits) { + return null; + } + + const milliseconds = moment.duration(intervalSize, intervalUnits).asMilliseconds(); + const esFormat = intervalSize + intervalUnits; + + return { + milliseconds, + esFormat, + }; + }; + + const intervalValues = { + hot: getValueFor('hot'), + warm: getValueFor('warm'), + cold: getValueFor('cold'), + }; + + const checkIfGreaterAndMultiple = (nextInterval: number, previousInterval: number): boolean => + nextInterval > previousInterval && nextInterval % previousInterval === 0; + + if (phase === 'warm' && intervalValues.warm) { + if (intervalValues.hot) { + if ( + !checkIfGreaterAndMultiple( + intervalValues.warm.milliseconds, + intervalValues.hot.milliseconds + ) + ) { + return { + message: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.downsamplePreviousIntervalWarmPhaseError', + { + defaultMessage: + 'Must be greater than and a multiple of the hot phase value ({value})', + values: { + value: intervalValues.hot.esFormat, + }, + } + ), + }; + } + } + } + + if (phase === 'cold' && intervalValues.cold) { + if (intervalValues.warm) { + if ( + !checkIfGreaterAndMultiple( + intervalValues.cold.milliseconds, + intervalValues.warm.milliseconds + ) + ) { + return { + message: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.downsamplePreviousIntervalColdPhaseWarmError', + { + defaultMessage: + 'Must be greater than and a multiple of the warm phase value ({value})', + values: { + value: intervalValues.warm.esFormat, + }, + } + ), + }; + } + } else if (intervalValues.hot) { + if ( + !checkIfGreaterAndMultiple( + intervalValues.cold.milliseconds, + intervalValues.hot.milliseconds + ) + ) { + return { + message: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.downsamplePreviousIntervalColdPhaseHotError', + { + defaultMessage: + 'Must be greater than and a multiple of the hot phase value ({value})', + values: { + value: intervalValues.hot.esFormat, + }, + } + ), + }; + } + } + } + }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index f54a782ea1334..b4d9c5282d678 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -54,6 +54,21 @@ export const i18nTexts = { readonlyEnabledFieldLabel: i18n.translate('xpack.indexLifecycleMgmt.readonlyFieldLabel', { defaultMessage: 'Make index read only', }), + downsampleEnabledFieldLabel: i18n.translate('xpack.indexLifecycleMgmt.downsampleFieldLabel', { + defaultMessage: 'Enable downsampling', + }), + downsampleIntervalFieldLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.downsampleIntervalFieldLabel', + { + defaultMessage: 'Downsampling interval', + } + ), + downsampleIntervalFieldUnitsLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.downsampleIntervalFieldUnitsLabel', + { + defaultMessage: 'Downsampling interval units', + } + ), maxNumSegmentsFieldLabel: i18n.translate( 'xpack.indexLifecycleMgmt.forceMerge.numberOfSegmentsLabel', { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index 8e83f123a8fa2..5dd5477cae2c2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -29,7 +29,15 @@ interface ShrinkFields { }; } -interface HotPhaseMetaFields extends ForcemergeFields, ShrinkFields { +export interface DownsampleFields { + downsample: { + enabled: boolean; + fixedIntervalSize?: string; + fixedIntervalUnits?: string; + }; +} + +interface HotPhaseMetaFields extends ForcemergeFields, ShrinkFields, DownsampleFields { /** * By default rollover is enabled with set values for max age, max size and max docs. In this policy form * opting in to default rollover overrides custom rollover values. @@ -58,13 +66,14 @@ interface WarmPhaseMetaFields extends DataAllocationMetaFields, MinAgeField, ForcemergeFields, - ShrinkFields { + ShrinkFields, + DownsampleFields { enabled: boolean; warmPhaseOnRollover: boolean; readonlyEnabled: boolean; } -interface ColdPhaseMetaFields extends DataAllocationMetaFields, MinAgeField { +interface ColdPhaseMetaFields extends DataAllocationMetaFields, MinAgeField, DownsampleFields { enabled: boolean; readonlyEnabled: boolean; } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 01bbf59adb16f..c10ded1c3e623 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -15037,7 +15037,6 @@ "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.repositoryHelpText": "Chaque phase utilise le même référentiel de snapshot.", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.storageHelpText": "Type de snapshot installé pour le snapshot qu’il est possible de rechercher. Il s'agit d'une option avancée. Ne la modifiez que si vous savez ce que vous faites.", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.storageLabel": "Stockage", - "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotCalloutBody": "Les actions Forcer la fusion, Réduire et Lecture seule ne sont pas autorisées lors de la conversion des données en index entièrement installé dans cette phase.", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotLicenseCalloutBody": "Licence Enterprise obligatoire pour créer un snapshot qu’il est possible de rechercher.", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotLicenseCalloutTitle": "Licence Enterprise requise", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotRepoFieldLabel": "Référentiel de snapshot", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f6dbbfc735890..a6a6a929da9dd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15025,7 +15025,6 @@ "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.repositoryHelpText": "各フェーズは同じスナップショットリポジトリを使用します。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.storageHelpText": "検索可能なスナップショットにマウントされたスナップショットのタイプ。これは高度なオプションです。作業内容を理解している場合にのみ変更してください。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.storageLabel": "ストレージ", - "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotCalloutBody": "このフェーズでデータを完全にマウントされたインデックスに変換するときには、強制マージ、縮小、読み取り専用アクションは許可されません。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotLicenseCalloutBody": "検索可能なスナップショットを作成するには、エンタープライズライセンスが必要です。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotLicenseCalloutTitle": "エンタープライズライセンスが必要です", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotRepoFieldLabel": "スナップショットリポジトリ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 60c556135b465..bd0b538fd94dd 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15042,7 +15042,6 @@ "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.repositoryHelpText": "每个阶段使用相同的快照存储库。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.storageHelpText": "为可搜索快照安装的快照类型。这是高级选项。只有了解此功能时才能更改。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.storageLabel": "存储", - "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotCalloutBody": "在此阶段将数据转换为完全安装的索引时,不允许强制合并、缩小和只读操作。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotLicenseCalloutBody": "要创建可搜索快照,需要企业许可证。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotLicenseCalloutTitle": "需要企业许可证", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotRepoFieldLabel": "快照存储库", From b95485e28ddbf44553b385d2240094f334a8800e Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 13 Sep 2022 14:55:50 +0200 Subject: [PATCH 06/28] [Fleet] Improve fleet package policy delete API (#140541) --- .../plugins/fleet/server/errors/handlers.ts | 3 +- .../fleet/server/services/package_policy.ts | 86 +++++++++++++------ 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index da6d245f456a6..173d295c079c2 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -29,6 +29,7 @@ import { RegistryError, RegistryResponseError, PackageFailedVerificationError, + PackagePolicyNotFoundError, } from '.'; type IngestErrorHandler = ( @@ -52,7 +53,7 @@ const getHTTPResponseCode = (error: IngestManagerError): number => { // Connection errors (ie. RegistryConnectionError) / fallback (RegistryError) from EPR return 502; // Bad Gateway } - if (error instanceof PackageNotFoundError) { + if (error instanceof PackageNotFoundError || error instanceof PackagePolicyNotFoundError) { return 404; // Not Found } if (error instanceof AgentPolicyNameExistsError) { diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 2d02e0e710f77..6cd6f373c1104 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -9,7 +9,6 @@ import { omit, partition, isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; import semverLt from 'semver/functions/lt'; import { getFlattenedObject } from '@kbn/std'; -import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { KibanaRequest, ElasticsearchClient, @@ -24,6 +23,8 @@ import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; import type { AuthenticatedUser } from '@kbn/security-plugin/server'; +import pMap from 'p-map'; + import { packageToPackagePolicy, packageToPackagePolicyInputs, @@ -552,42 +553,54 @@ class PackagePolicyService implements PackagePolicyServiceInterface { ): Promise { const result: DeletePackagePoliciesResponse = []; - for (const id of ids) { - try { - const packagePolicy = await this.get(soClient, id); - if (!packagePolicy) { - throw new Error('Package policy not found'); - } + const packagePolicies = await this.getByIDs(soClient, ids, { ignoreMissing: true }); + if (!packagePolicies) { + return []; + } - if (packagePolicy.is_managed && !options?.force) { - throw new PackagePolicyRestrictionRelatedError(`Cannot delete package policy ${id}`); - } + const uniqueAgentPolicyIds = [ + ...new Set(packagePolicies.map((packagePolicy) => packagePolicy.policy_id)), + ]; + + const hostedAgentPolicies: string[] = []; + for (const agentPolicyId of uniqueAgentPolicyIds) { + try { await validateIsNotHostedPolicy( soClient, - packagePolicy?.policy_id, + agentPolicyId, options?.force, 'Cannot remove integrations of hosted agent policy' ); + } catch (e) { + hostedAgentPolicies.push(agentPolicyId); + } + } - const agentPolicy = await agentPolicyService - .get(soClient, packagePolicy.policy_id) - .catch((err) => { - if (SavedObjectsErrorHelpers.isNotFoundError(err)) { - appContextService - .getLogger() - .warn(`Agent policy ${packagePolicy.policy_id} not found`); - return null; - } - throw err; - }); + const deletePackagePolicy = async (id: string) => { + try { + const packagePolicy = packagePolicies.find((p) => p.id === id); - await soClient.delete(SAVED_OBJECT_TYPE, id); - if (agentPolicy && !options?.skipUnassignFromAgentPolicies) { - await agentPolicyService.bumpRevision(soClient, esClient, packagePolicy.policy_id, { - user: options?.user, - }); + if (!packagePolicy) { + throw new PackagePolicyNotFoundError( + `Saved object [ingest-package-policies/${id}] not found` + ); + } + + if (packagePolicy.is_managed && !options?.force) { + throw new PackagePolicyRestrictionRelatedError(`Cannot delete package policy ${id}`); } + + if (hostedAgentPolicies.includes(packagePolicy.policy_id)) { + throw new HostedAgentPolicyRestrictionRelatedError( + 'Cannot remove integrations of hosted agent policy' + ); + } + + // TODO: replace this with savedObject BulkDelete when following PR is merged + // https://github.com/elastic/kibana/pull/139680 + await soClient.delete(SAVED_OBJECT_TYPE, id); + result.push({ id, name: packagePolicy.name, @@ -606,6 +619,25 @@ class PackagePolicyService implements PackagePolicyServiceInterface { ...ingestErrorToResponseOptions(error), }); } + }; + + await pMap(ids, deletePackagePolicy, { concurrency: 1000 }); + + if (!options?.skipUnassignFromAgentPolicies) { + const uniquePolicyIdsR = [ + ...new Set(result.filter((r) => r.success && r.policy_id).map((r) => r.policy_id!)), + ]; + + const agentPolicies = await agentPolicyService.getByIDs(soClient, uniquePolicyIdsR); + + for (const policyId of uniquePolicyIdsR) { + const agentPolicy = agentPolicies.find((p) => p.id === policyId); + if (agentPolicy) { + await agentPolicyService.bumpRevision(soClient, esClient, policyId, { + user: options?.user, + }); + } + } } return result; From dfafa262c7caf20e7b5065220950cd1f72522531 Mon Sep 17 00:00:00 2001 From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com> Date: Tue, 13 Sep 2022 18:09:08 +0500 Subject: [PATCH 07/28] [Console] Update "Copy as cURL" to interpolate variables and strip request-body comments (#140262) * Interpolate variables and strip request-body comments in cURLs * Address CR change Co-authored-by: Muhammad Ibragimov Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/console_menu.tsx | 16 ++- .../console/public/application/index.tsx | 9 +- .../models/sense_editor/sense_editor.test.js | 108 ++++++++++++++++++ .../models/sense_editor/sense_editor.ts | 40 +++++-- src/plugins/console/public/lib/utils/index.ts | 2 +- src/plugins/console/public/services/index.ts | 2 +- .../console/public/services/storage.ts | 3 + 7 files changed, 163 insertions(+), 17 deletions(-) diff --git a/src/plugins/console/public/application/components/console_menu.tsx b/src/plugins/console/public/application/components/console_menu.tsx index 159c0d600308f..3f5113b3ac44f 100644 --- a/src/plugins/console/public/application/components/console_menu.tsx +++ b/src/plugins/console/public/application/components/console_menu.tsx @@ -31,6 +31,7 @@ interface Props { interface State { isPopoverOpen: boolean; curlCode: string; + curlError: Error | null; } export class ConsoleMenu extends Component { @@ -40,14 +41,20 @@ export class ConsoleMenu extends Component { this.state = { curlCode: '', isPopoverOpen: false, + curlError: null, }; } mouseEnter = () => { if (this.state.isPopoverOpen) return; - this.props.getCurl().then((text) => { - this.setState({ curlCode: text }); - }); + this.props + .getCurl() + .then((text) => { + this.setState({ curlCode: text, curlError: null }); + }) + .catch((e) => { + this.setState({ curlError: e }); + }); }; async copyAsCurl() { @@ -69,6 +76,9 @@ export class ConsoleMenu extends Component { } async copyText(text: string) { + if (this.state.curlError) { + throw this.state.curlError; + } if (window.navigator?.clipboard) { await window.navigator.clipboard.writeText(text); return; diff --git a/src/plugins/console/public/application/index.tsx b/src/plugins/console/public/application/index.tsx index e9f37c232eeaa..1cf9a54210973 100644 --- a/src/plugins/console/public/application/index.tsx +++ b/src/plugins/console/public/application/index.tsx @@ -19,7 +19,13 @@ import { import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { KibanaThemeProvider } from '../shared_imports'; -import { createStorage, createHistory, createSettings, AutocompleteInfo } from '../services'; +import { + createStorage, + createHistory, + createSettings, + AutocompleteInfo, + setStorage, +} from '../services'; import { createUsageTracker } from '../services/tracker'; import * as localStorageObjectClient from '../lib/local_storage_object_client'; import { Main } from './containers'; @@ -56,6 +62,7 @@ export function renderApp({ engine: window.localStorage, prefix: 'sense:', }); + setStorage(storage); const history = createHistory({ storage }); const settings = createSettings({ storage }); const objectStorageClient = localStorageObjectClient.create(storage); diff --git a/src/plugins/console/public/application/models/sense_editor/sense_editor.test.js b/src/plugins/console/public/application/models/sense_editor/sense_editor.test.js index 4751d3ca29863..c6f484e8f1697 100644 --- a/src/plugins/console/public/application/models/sense_editor/sense_editor.test.js +++ b/src/plugins/console/public/application/models/sense_editor/sense_editor.test.js @@ -15,6 +15,7 @@ import { URL } from 'url'; import { create } from './create'; import { XJson } from '@kbn/es-ui-shared-plugin/public'; import editorInput1 from './__fixtures__/editor_input1.txt'; +import { setStorage, createStorage } from '../../../services'; const { collapseLiteralStrings } = XJson; @@ -22,6 +23,7 @@ describe('Editor', () => { let input; let oldUrl; let olldWindow; + let storage; beforeEach(function () { // Set up our document body @@ -43,12 +45,18 @@ describe('Editor', () => { origin: 'http://localhost:5620', }, }); + storage = createStorage({ + engine: global.window.localStorage, + prefix: 'console_test', + }); + setStorage(storage); }); afterEach(function () { global.URL = oldUrl; global.window = olldWindow; $(input.getCoreEditor().getContainer()).hide(); input.autocomplete._test.addChangeListener(); + setStorage(null); }); let testCount = 0; @@ -506,4 +514,104 @@ curl -XPOST "http://localhost:9200/_sql?format=txt" -H "kbn-xsrf: reporting" -H ` curl -XGET "http://localhost:5620/api/spaces/space" -H \"kbn-xsrf: reporting\"`.trim() ); + + describe('getRequestsAsCURL', () => { + it('should return empty string if no requests', async () => { + input?.getCoreEditor().setValue('', false); + const curl = await input.getRequestsAsCURL('http://localhost:9200', { + start: { lineNumber: 1 }, + end: { lineNumber: 1 }, + }); + expect(curl).toEqual(''); + }); + + it('should replace variables in the URL', async () => { + storage.set('variables', [{ name: 'exampleVariableA', value: 'valueA' }]); + input?.getCoreEditor().setValue('GET ${exampleVariableA}', false); + const curl = await input.getRequestsAsCURL('http://localhost:9200', { + start: { lineNumber: 1 }, + end: { lineNumber: 1 }, + }); + expect(curl).toContain('valueA'); + }); + + it('should replace variables in the body', async () => { + storage.set('variables', [{ name: 'exampleVariableB', value: 'valueB' }]); + console.log(storage.get('variables')); + input + ?.getCoreEditor() + .setValue('GET _search\n{\t\t"query": {\n\t\t\t"${exampleVariableB}": ""\n\t}\n}', false); + const curl = await input.getRequestsAsCURL('http://localhost:9200', { + start: { lineNumber: 1 }, + end: { lineNumber: 6 }, + }); + expect(curl).toContain('valueB'); + }); + + it('should strip comments in the URL', async () => { + input?.getCoreEditor().setValue('GET _search // comment', false); + const curl = await input.getRequestsAsCURL('http://localhost:9200', { + start: { lineNumber: 1 }, + end: { lineNumber: 6 }, + }); + expect(curl).not.toContain('comment'); + }); + + it('should strip comments in the body', async () => { + input + ?.getCoreEditor() + .setValue('{\n\t"query": {\n\t\t"match_all": {} // comment \n\t}\n}', false); + const curl = await input.getRequestsAsCURL('http://localhost:9200', { + start: { lineNumber: 1 }, + end: { lineNumber: 8 }, + }); + console.log('curl', curl); + expect(curl).not.toContain('comment'); + }); + + it('should strip multi-line comments in the body', async () => { + input + ?.getCoreEditor() + .setValue('{\n\t"query": {\n\t\t"match_all": {} /* comment */\n\t}\n}', false); + const curl = await input.getRequestsAsCURL('http://localhost:9200', { + start: { lineNumber: 1 }, + end: { lineNumber: 8 }, + }); + console.log('curl', curl); + expect(curl).not.toContain('comment'); + }); + + it('should replace multiple variables in the URL', async () => { + storage.set('variables', [ + { name: 'exampleVariableA', value: 'valueA' }, + { name: 'exampleVariableB', value: 'valueB' }, + ]); + input?.getCoreEditor().setValue('GET ${exampleVariableA}/${exampleVariableB}', false); + const curl = await input.getRequestsAsCURL('http://localhost:9200', { + start: { lineNumber: 1 }, + end: { lineNumber: 1 }, + }); + expect(curl).toContain('valueA'); + expect(curl).toContain('valueB'); + }); + + it('should replace multiple variables in the body', async () => { + storage.set('variables', [ + { name: 'exampleVariableA', value: 'valueA' }, + { name: 'exampleVariableB', value: 'valueB' }, + ]); + input + ?.getCoreEditor() + .setValue( + 'GET _search\n{\t\t"query": {\n\t\t\t"${exampleVariableA}": "${exampleVariableB}"\n\t}\n}', + false + ); + const curl = await input.getRequestsAsCURL('http://localhost:9200', { + start: { lineNumber: 1 }, + end: { lineNumber: 6 }, + }); + expect(curl).toContain('valueA'); + expect(curl).toContain('valueB'); + }); + }); }); diff --git a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts index 50ee1cd1d262a..ac2205205ab46 100644 --- a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts +++ b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts @@ -7,7 +7,7 @@ */ import _ from 'lodash'; - +import { parse } from 'hjson'; import { XJson } from '@kbn/es-ui-shared-plugin/public'; import RowParser from '../../../lib/row_parser'; @@ -19,6 +19,8 @@ import { constructUrl } from '../../../lib/es/es'; import { CoreEditor, Position, Range } from '../../../types'; import { createTokenIterator } from '../../factories'; import createAutocompleter from '../../../lib/autocomplete/autocomplete'; +import { getStorage, StorageKeys } from '../../../services'; +import { DEFAULT_VARIABLES } from '../../../../common/constants'; const { collapseLiteralStrings } = XJson; @@ -474,7 +476,9 @@ export class SenseEditor { }, 25); getRequestsAsCURL = async (elasticsearchBaseUrl: string, range?: Range): Promise => { - const requests = await this.getRequestsInRange(range, true); + const variables = getStorage().get(StorageKeys.VARIABLES, DEFAULT_VARIABLES); + let requests = await this.getRequestsInRange(range, true); + requests = utils.replaceVariables(requests, variables); const result = _.map(requests, (req) => { if (typeof req === 'string') { // no request block @@ -490,16 +494,30 @@ export class SenseEditor { // Append 'kbn-xsrf' header to bypass (XSRF/CSRF) protections let ret = `curl -X${method.toUpperCase()} "${url}" -H "kbn-xsrf: reporting"`; + if (data && data.length) { - ret += ` -H "Content-Type: application/json" -d'\n`; - const dataAsString = collapseLiteralStrings(data.join('\n')); - - // We escape single quoted strings that that are wrapped in single quoted strings - ret += dataAsString.replace(/'/g, "'\\''"); - if (data.length > 1) { - ret += '\n'; - } // end with a new line - ret += "'"; + const joinedData = data.join('\n'); + let dataAsString: string; + + try { + ret += ` -H "Content-Type: application/json" -d'\n`; + + if (utils.hasComments(joinedData)) { + // if there are comments in the data, we need to strip them out + const dataWithoutComments = parse(joinedData); + dataAsString = collapseLiteralStrings(JSON.stringify(dataWithoutComments, null, 2)); + } else { + dataAsString = collapseLiteralStrings(joinedData); + } + // We escape single quoted strings that are wrapped in single quoted strings + ret += dataAsString.replace(/'/g, "'\\''"); + if (data.length > 1) { + ret += '\n'; + } // end with a new line + ret += "'"; + } catch (e) { + throw new Error(`Error parsing data: ${e.message}`); + } } return ret; }); diff --git a/src/plugins/console/public/lib/utils/index.ts b/src/plugins/console/public/lib/utils/index.ts index 2495f63c7614f..dfa513085019d 100644 --- a/src/plugins/console/public/lib/utils/index.ts +++ b/src/plugins/console/public/lib/utils/index.ts @@ -130,7 +130,7 @@ export const replaceVariables = ( }); } - if (req.data.length) { + if (req.data && req.data.length) { if (bodyRegex.test(req.data[0])) { const data = req.data[0].replaceAll(bodyRegex, (match) => { // Sanitize variable name diff --git a/src/plugins/console/public/services/index.ts b/src/plugins/console/public/services/index.ts index 2447ab1438ba4..d73c169fd647a 100644 --- a/src/plugins/console/public/services/index.ts +++ b/src/plugins/console/public/services/index.ts @@ -7,7 +7,7 @@ */ export { createHistory, History } from './history'; -export { createStorage, Storage, StorageKeys } from './storage'; +export { createStorage, Storage, StorageKeys, setStorage, getStorage } from './storage'; export type { DevToolsSettings } from './settings'; export { createSettings, Settings, DEFAULT_SETTINGS } from './settings'; export { AutocompleteInfo, getAutocompleteInfo, setAutocompleteInfo } from './autocomplete'; diff --git a/src/plugins/console/public/services/storage.ts b/src/plugins/console/public/services/storage.ts index 4b4d051607b8d..b38cc2925dfb1 100644 --- a/src/plugins/console/public/services/storage.ts +++ b/src/plugins/console/public/services/storage.ts @@ -7,6 +7,7 @@ */ import { transform, keys, startsWith } from 'lodash'; +import { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; type IStorageEngine = typeof window.localStorage; @@ -71,3 +72,5 @@ export class Storage { export function createStorage(deps: { engine: IStorageEngine; prefix: string }) { return new Storage(deps.engine, deps.prefix); } + +export const [getStorage, setStorage] = createGetterSetter('storage'); From 2be75d572999fb703ba00261640daa65d791d921 Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Tue, 13 Sep 2022 15:22:18 +0200 Subject: [PATCH 08/28] [Fleet] Display category badge besides integrations list searchbar (#140391) * [Fleet] Display category badge besides integrations list searchbar * Fix padding in button * Avoid clearing query when clearing category * Fix cypress test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../integration/integrations_real.spec.ts | 81 +++++++++++----- .../fleet/cypress/screens/integrations.ts | 8 +- .../epm/components/package_list_grid.tsx | 95 ++++++++++++------- .../epm/screens/home/category_facets.tsx | 1 + 4 files changed, 122 insertions(+), 63 deletions(-) diff --git a/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts b/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts index 34fa5b7af55ca..c3bee2d758df0 100644 --- a/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts @@ -22,14 +22,49 @@ import { POLICIES_TAB, SETTINGS_TAB, UPDATE_PACKAGE_BTN, - INTEGRATIONS_SEARCHBAR_INPUT, + INTEGRATIONS_SEARCHBAR, SETTINGS, INTEGRATION_POLICIES_UPGRADE_CHECKBOX, + INTEGRATION_LIST, + getIntegrationCategories, } from '../screens/integrations'; import { LOADING_SPINNER, CONFIRM_MODAL } from '../screens/navigation'; import { ADD_PACKAGE_POLICY_BTN } from '../screens/fleet'; import { cleanupAgentPolicies } from '../tasks/cleanup'; +function setupIntegrations() { + cy.intercept( + '/api/fleet/epm/packages?*', + { + middleware: true, + }, + (req) => { + req.on('before:response', (res) => { + // force all API responses to not be cached + res.headers['cache-control'] = 'no-store'; + }); + } + ).as('packages'); + + navigateTo(INTEGRATIONS); + cy.wait('@packages'); +} + +it('should install integration without policy', () => { + cy.visit('/app/integrations/detail/tomcat/settings'); + + cy.getBySel(SETTINGS.INSTALL_ASSETS_BTN).click(); + cy.get('.euiCallOut').contains('This action will install 1 assets'); + cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click(); + + cy.getBySel(LOADING_SPINNER).should('not.exist'); + + cy.getBySel(SETTINGS.UNINSTALL_ASSETS_BTN).click(); + cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click(); + cy.getBySel(LOADING_SPINNER).should('not.exist'); + cy.getBySel(SETTINGS.INSTALL_ASSETS_BTN).should('exist'); +}); + describe('Add Integration - Real API', () => { const integration = 'apache'; @@ -41,29 +76,6 @@ describe('Add Integration - Real API', () => { cleanupAgentPolicies(); }); - function addAndVerifyIntegration() { - cy.intercept( - '/api/fleet/epm/packages?*', - { - middleware: true, - }, - (req) => { - req.on('before:response', (res) => { - // force all API responses to not be cached - res.headers['cache-control'] = 'no-store'; - }); - } - ).as('packages'); - - navigateTo(INTEGRATIONS); - cy.wait('@packages'); - cy.getBySel(LOADING_SPINNER).should('not.exist'); - cy.getBySel(INTEGRATIONS_SEARCHBAR_INPUT).type('Apache'); - cy.getBySel(getIntegrationCard(integration)).click(); - addIntegration(); - cy.getBySel(INTEGRATION_NAME_LINK).contains('apache-1'); - } - it('should install integration without policy', () => { cy.visit('/app/integrations/detail/tomcat/settings'); @@ -80,7 +92,12 @@ describe('Add Integration - Real API', () => { }); it('should display Apache integration in the Policies list once installed ', () => { - addAndVerifyIntegration(); + setupIntegrations(); + cy.getBySel(LOADING_SPINNER).should('not.exist'); + cy.getBySel(INTEGRATIONS_SEARCHBAR.INPUT).clear().type('Apache'); + cy.getBySel(getIntegrationCard(integration)).click(); + addIntegration(); + cy.getBySel(INTEGRATION_NAME_LINK).contains('apache-1'); cy.getBySel(AGENT_POLICY_NAME_LINK).contains('Agent policy 1'); }); @@ -118,7 +135,7 @@ describe('Add Integration - Real API', () => { cy.getBySel(ADD_PACKAGE_POLICY_BTN).click(); cy.wait('@packages'); cy.getBySel(LOADING_SPINNER).should('not.exist'); - cy.getBySel(INTEGRATIONS_SEARCHBAR_INPUT).type('Apache'); + cy.getBySel(INTEGRATIONS_SEARCHBAR.INPUT).clear().type('Apache'); cy.getBySel(getIntegrationCard(integration)).click(); addIntegration({ useExistingPolicy: true }); cy.get('.euiBasicTable-loading').should('not.exist'); @@ -152,4 +169,16 @@ describe('Add Integration - Real API', () => { cy.getBySel(PACKAGE_VERSION).contains(newVersion); }); }); + + it('should filter integrations by category', () => { + setupIntegrations(); + cy.getBySel(getIntegrationCategories('aws')).click(); + cy.getBySel(INTEGRATIONS_SEARCHBAR.BADGE).contains('AWS').should('exist'); + cy.getBySel(INTEGRATION_LIST).find('.euiCard').should('have.length', 30); + + cy.getBySel(INTEGRATIONS_SEARCHBAR.INPUT).clear().type('Cloud'); + cy.getBySel(INTEGRATION_LIST).find('.euiCard').should('have.length', 3); + cy.getBySel(INTEGRATIONS_SEARCHBAR.REMOVE_BADGE_BUTTON).click(); + cy.getBySel(INTEGRATIONS_SEARCHBAR.BADGE).should('not.exist'); + }); }); diff --git a/x-pack/plugins/fleet/cypress/screens/integrations.ts b/x-pack/plugins/fleet/cypress/screens/integrations.ts index 3915c6600baaa..63b13d8ff7fd3 100644 --- a/x-pack/plugins/fleet/cypress/screens/integrations.ts +++ b/x-pack/plugins/fleet/cypress/screens/integrations.ts @@ -23,7 +23,12 @@ export const LATEST_VERSION = 'epmSettings.latestVersionTitle'; export const INSTALLED_VERSION = 'epmSettings.installedVersionTitle'; export const PACKAGE_VERSION = 'packageVersionText'; -export const INTEGRATIONS_SEARCHBAR_INPUT = 'epmList.searchBar'; +export const INTEGRATION_LIST = 'epmList.integrationCards'; +export const INTEGRATIONS_SEARCHBAR = { + INPUT: 'epmList.searchBar', + BADGE: 'epmList.categoryBadge', + REMOVE_BADGE_BUTTON: 'epmList.categoryBadge.closeBtn', +}; export const SETTINGS = { INSTALL_ASSETS_BTN: 'installAssetsButton', @@ -33,3 +38,4 @@ export const SETTINGS = { export const INTEGRATION_POLICIES_UPGRADE_CHECKBOX = 'epmDetails.upgradePoliciesCheckbox'; export const getIntegrationCard = (integration: string) => `integration-card:epr:${integration}`; +export const getIntegrationCategories = (category: string) => `epmList.categories.${category}`; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx index 98efc91cfc34c..e360f615ad369 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx @@ -15,9 +15,11 @@ import { EuiLink, EuiSpacer, EuiTitle, - EuiSearchBar, + EuiFieldSearch, EuiText, - EuiBadge, + useEuiTheme, + EuiIcon, + EuiScreenReaderOnly, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -69,6 +71,7 @@ export const PackageListGrid: FunctionComponent = ({ const menuRef = useRef(null); const [isSticky, setIsSticky] = useState(false); const [windowScrollY] = useState(window.scrollY); + const { euiTheme } = useEuiTheme(); useEffect(() => { const menuRefCurrent = menuRef.current; @@ -81,17 +84,10 @@ export const PackageListGrid: FunctionComponent = ({ return () => window.removeEventListener('scroll', onScroll); }, [windowScrollY, isSticky]); - const onQueryChange = ({ - queryText, - error, - }: { - queryText: string; - error: { message: string } | null; - }) => { - if (!error) { - onSearchChange(queryText); - setSearchTerm(queryText); - } + const onQueryChange = (e: any) => { + const queryText = e.target.value; + setSearchTerm(queryText); + onSearchChange(queryText); }; const resetQuery = () => { @@ -128,37 +124,64 @@ export const PackageListGrid: FunctionComponent = ({ <> {featuredList}
- + {controlsContent} - onQueryChange(e)} + isClearable={true} + incremental={true} + fullWidth={true} + prepend={ selectedCategoryTitle ? ( -
- { + + + Searching category: + + {selectedCategoryTitle} +
+ + + ) : undefined } /> diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx index 573701ae9a6fb..9f88e2c391b3d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx @@ -64,6 +64,7 @@ export function CategoryFacets({ categories.map((category) => { return ( Date: Tue, 13 Sep 2022 15:32:06 +0200 Subject: [PATCH 09/28] Adds policy generic error from agent components in fleet agent details (#140594) --- .../package_action_formatter.ts | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts b/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts index 57117c9b1397c..f7796f8ef4c5c 100644 --- a/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts +++ b/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { DocLinks } from '@kbn/doc-links'; -type PackageActions = 'es_connection'; +type PackageActions = 'es_connection' | 'policy_failure'; export const titles = Object.freeze( new Map([ @@ -18,6 +18,15 @@ export const titles = Object.freeze( defaultMessage: 'Elasticsearch connection failure', }), ], + [ + 'policy_failure', + i18n.translate( + 'xpack.securitySolution.endpoint.details.packageActions.policy_failure.title', + { + defaultMessage: 'Policy response failure', + } + ), + ], ]) ); @@ -33,6 +42,16 @@ export const descriptions = Object.freeze( } ), ], + [ + 'policy_failure', + i18n.translate( + 'xpack.securitySolution.endpoint.details.packageActions.policy_failure.description', + { + defaultMessage: + 'The Endpoint did not apply the Policy correctly. Expand the Policy response above for more details.', + } + ), + ], ]) ); @@ -68,12 +87,16 @@ export class PackageActionFormatter { } public get linkUrl(): string { - return this.docLinks[this.key]; + return this.docLinks[ + this.key as keyof DocLinks['securitySolution']['packageActionTroubleshooting'] + ]; } private getKeyFromErrorCode(code: number): PackageActions { if (code === 123) { return 'es_connection'; + } else if (code === 124) { + return 'policy_failure'; } else { throw new Error(`Invalid error code ${code}`); } From 7e90e71aaa3329c5d6c133337be30631c3383cf3 Mon Sep 17 00:00:00 2001 From: Baturalp Gurdin <9674241+suchcodemuchwow@users.noreply.github.com> Date: Tue, 13 Sep 2022 16:34:03 +0300 Subject: [PATCH 10/28] skip flaky jest suite (#140618) --- .../pages/endpoint_hosts/view/components/search_bar.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx index eb651d8aedd12..57611fcdf2484 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx @@ -16,7 +16,8 @@ import { fireEvent } from '@testing-library/dom'; import { uiQueryParams } from '../../store/selectors'; import type { EndpointIndexUIQueryParams } from '../../types'; -describe('when rendering the endpoint list `AdminSearchBar`', () => { +// FLAKY: https://github.com/elastic/kibana/issues/140618 +describe.skip('when rendering the endpoint list `AdminSearchBar`', () => { let render: ( urlParams?: EndpointIndexUIQueryParams ) => Promise>; From 21aa27c8a84b090f1e011516b9f941ec0faf0a83 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 13 Sep 2022 15:35:34 +0200 Subject: [PATCH 11/28] [ML] Fix cross-boundary imports. (#140470) Fix violations for cross-boundary imports. --- x-pack/plugins/ml/__mocks__/shared_imports.ts | 8 -------- .../create_analytics_button.test.tsx | 2 -- .../hooks/use_create_analytics_form/reducer.ts | 8 ++++++-- .../edit_job_flyout/edit_job_flyout.js | 4 +++- .../components/ml_job_editor/ml_job_editor.tsx | 11 +++++------ .../json_editor_flyout/json_editor_flyout.tsx | 4 +++- x-pack/plugins/ml/shared_imports.ts | 17 ----------------- x-pack/plugins/ml/tsconfig.json | 1 - 8 files changed, 17 insertions(+), 38 deletions(-) delete mode 100644 x-pack/plugins/ml/__mocks__/shared_imports.ts delete mode 100644 x-pack/plugins/ml/shared_imports.ts diff --git a/x-pack/plugins/ml/__mocks__/shared_imports.ts b/x-pack/plugins/ml/__mocks__/shared_imports.ts deleted file mode 100644 index 43494fea6992a..0000000000000 --- a/x-pack/plugins/ml/__mocks__/shared_imports.ts +++ /dev/null @@ -1,8 +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 const XJsonMode = jest.fn(); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx index c77a254be2f10..9b3f86070a6cb 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx @@ -10,8 +10,6 @@ import React from 'react'; import { CreateAnalyticsButton } from './create_analytics_button'; -jest.mock('../../../../../../../shared_imports'); - describe('Data Frame Analytics: ', () => { test('Minimal initialization', () => { const wrapper = mount( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 58a471b4e7246..aa25bf2c8eb3d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -7,12 +7,14 @@ import { i18n } from '@kbn/i18n'; import { memoize, isEqual } from 'lodash'; + // @ts-ignore import numeral from '@elastic/numeral'; + import { indexPatterns } from '@kbn/data-plugin/public'; -import { isValidIndexName } from '../../../../../../../common/util/es_utils'; +import { XJson } from '@kbn/es-ui-shared-plugin/public'; -import { collapseLiteralStrings } from '../../../../../../../shared_imports'; +import { isValidIndexName } from '../../../../../../../common/util/es_utils'; import { Action, ACTION } from './actions'; import { @@ -48,6 +50,8 @@ import { } from '../../../../common/analytics'; import { isAdvancedConfig } from '../../components/action_clone/clone_action_name'; +const { collapseLiteralStrings } = XJson; + const mmlAllowedUnitsStr = `${ALLOWED_DATA_UNITS.slice(0, ALLOWED_DATA_UNITS.length - 1).join( ', ' )} or ${[...ALLOWED_DATA_UNITS].pop()}`; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js index 4fa5cdcb7c9f7..9100b7ffa03ab 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js @@ -32,11 +32,13 @@ import { validateModelMemoryLimit, validateGroupNames, isValidCustomUrls } from import { toastNotificationServiceProvider } from '../../../../services/toast_notification_service'; import { ml } from '../../../../services/ml_api_service'; import { withKibana } from '@kbn/kibana-react-plugin/public'; -import { collapseLiteralStrings } from '../../../../../../shared_imports'; +import { XJson } from '@kbn/es-ui-shared-plugin/public'; import { DATAFEED_STATE, JOB_STATE } from '../../../../../../common/constants/states'; import { isManagedJob } from '../../../jobs_utils'; import { ManagedJobsWarningCallout } from '../confirm_modals/managed_jobs_warning_callout'; +const { collapseLiteralStrings } = XJson; + export class EditJobFlyoutUI extends Component { _initialJobFormState = null; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/ml_job_editor/ml_job_editor.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/ml_job_editor/ml_job_editor.tsx index 631b916202505..bbe91f77a7204 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/ml_job_editor/ml_job_editor.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/ml_job_editor/ml_job_editor.tsx @@ -6,13 +6,12 @@ */ import React, { FC } from 'react'; +import { XJsonMode } from '@kbn/ace'; -import { - expandLiteralStrings, - XJsonMode, - EuiCodeEditor, - EuiCodeEditorProps, -} from '../../../../../../shared_imports'; +import { EuiCodeEditor, XJson } from '@kbn/es-ui-shared-plugin/public'; +import type { EuiCodeEditorProps } from '@kbn/es-ui-shared-plugin/public'; + +const { expandLiteralStrings } = XJson; export const ML_EDITOR_MODE = { TEXT: 'text', JSON: 'json', XJSON: new XJsonMode() }; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx index 5b16462300446..b9e77a3a5f273 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx @@ -20,7 +20,7 @@ import { EuiSpacer, EuiCallOut, } from '@elastic/eui'; -import { collapseLiteralStrings } from '../../../../../../../../shared_imports'; +import { XJson } from '@kbn/es-ui-shared-plugin/public'; import { CombinedJob, Datafeed } from '../../../../../../../../common/types/anomaly_detection_jobs'; import { ML_EDITOR_MODE, MLJobEditor } from '../../../../../jobs_list/components/ml_job_editor'; import { isValidJson } from '../../../../../../../../common/util/validation_utils'; @@ -29,6 +29,8 @@ import { isAdvancedJobCreator } from '../../../../common/job_creator'; import { DatafeedPreview } from '../datafeed_preview_flyout'; import { useToastNotificationService } from '../../../../../../services/toast_notification_service'; +const { collapseLiteralStrings } = XJson; + export enum EDITOR_MODE { HIDDEN, READONLY, diff --git a/x-pack/plugins/ml/shared_imports.ts b/x-pack/plugins/ml/shared_imports.ts deleted file mode 100644 index 25f78cce55611..0000000000000 --- a/x-pack/plugins/ml/shared_imports.ts +++ /dev/null @@ -1,17 +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. - */ - -// eslint-disable-next-line @kbn/imports/no_boundary_crossing -export { EuiCodeEditor } from '@kbn/es-ui-shared-plugin/public'; -export type { EuiCodeEditorProps } from '@kbn/es-ui-shared-plugin/public'; - -// eslint-disable-next-line @kbn/imports/no_boundary_crossing -import { XJson } from '@kbn/es-ui-shared-plugin/public'; -const { collapseLiteralStrings, expandLiteralStrings } = XJson; - -export { XJsonMode } from '@kbn/ace'; -export { collapseLiteralStrings, expandLiteralStrings }; diff --git a/x-pack/plugins/ml/tsconfig.json b/x-pack/plugins/ml/tsconfig.json index 55651e2f81165..a99bc950ca445 100644 --- a/x-pack/plugins/ml/tsconfig.json +++ b/x-pack/plugins/ml/tsconfig.json @@ -11,7 +11,6 @@ "public/**/*", "server/**/*", "__mocks__/**/*", - "shared_imports.ts", "../../../typings/**/*", // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 "public/**/*.json", From acac4ca970fdc2f1f73ea376f59f0c8f0fe55d71 Mon Sep 17 00:00:00 2001 From: Bhavya RM Date: Tue, 13 Sep 2022 09:40:42 -0400 Subject: [PATCH 12/28] timepicker TSVB refactoring (#140557) --- x-pack/test/functional/apps/rollup_job/tsvb.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/test/functional/apps/rollup_job/tsvb.js b/x-pack/test/functional/apps/rollup_job/tsvb.js index 4ef918aab09e4..957b33618d9cf 100644 --- a/x-pack/test/functional/apps/rollup_job/tsvb.js +++ b/x-pack/test/functional/apps/rollup_job/tsvb.js @@ -21,6 +21,8 @@ export default function ({ getService, getPageObjects }) { 'visualBuilder', 'timePicker', ]); + const fromTime = 'Oct 15, 2019 @ 00:00:01.000'; + const toTime = 'Oct 15, 2019 @ 19:31:44.000'; describe('tsvb integration', function () { //Since rollups can only be created once with the same name (even if you delete it), @@ -40,10 +42,11 @@ export default function ({ getService, getPageObjects }) { await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json' ); - await kibanaServer.uiSettings.replace({ + await kibanaServer.uiSettings.update({ defaultIndex: 'rollup', + 'metrics:allowStringIndices': true, + 'timepicker:timeDefaults': `{ "from": "${fromTime}", "to": "${toTime}"}`, }); - await kibanaServer.uiSettings.update({ 'metrics:allowStringIndices': true }); }); it('create rollup tsvb', async () => { @@ -85,10 +88,6 @@ export default function ({ getService, getPageObjects }) { await PageObjects.visualBuilder.checkVisualBuilderIsPresent(); await PageObjects.visualBuilder.clickMetric(); await PageObjects.visualBuilder.checkMetricTabIsPresent(); - await PageObjects.timePicker.setAbsoluteRange( - 'Oct 15, 2019 @ 00:00:01.000', - 'Oct 15, 2019 @ 19:31:44.000' - ); await PageObjects.visualBuilder.clickPanelOptions('metric'); await PageObjects.visualBuilder.setIndexPatternValue(rollupTargetIndexName, false); await PageObjects.visualBuilder.selectIndexPatternTimeField('@timestamp'); @@ -112,6 +111,7 @@ export default function ({ getService, getPageObjects }) { 'x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json' ); await kibanaServer.uiSettings.update({ 'metrics:allowStringIndices': false }); + await kibanaServer.uiSettings.replace({}); await security.testUser.restoreDefaults(); }); }); From 4255bac100d468de874a7c9d6d83e403b43e3aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 13 Sep 2022 09:47:09 -0400 Subject: [PATCH 13/28] APM: converting params to destructured obj (#140450) * service: converting params to destructured obj * transaction: convert params to destructuring obj * span/error: convert to destructure obj * error * browser * fixing span --- packages/kbn-apm-synthtrace/README.md | 4 +- .../kbn-apm-synthtrace/src/lib/apm/browser.ts | 30 +++++++-- .../src/lib/apm/instance.ts | 29 ++++++++- .../kbn-apm-synthtrace/src/lib/apm/service.ts | 10 ++- .../kbn-apm-synthtrace/src/lib/apm/span.ts | 42 +++++-------- .../src/scenarios/aws_lambda.ts | 6 +- .../src/scenarios/distributed_trace.ts | 22 ++++--- .../src/scenarios/high_throughput.ts | 24 ++++--- .../src/scenarios/low_throughput.ts | 26 +++++--- .../src/scenarios/many_services.ts | 26 +++++--- .../src/scenarios/simple_trace.ts | 20 ++++-- .../src/scenarios/span_links.ts | 20 +++--- .../src/test/event_dsl_behavior.test.ts | 10 ++- .../src/test/rate_per_minute.test.ts | 10 ++- .../test/scenarios/01_simple_trace.test.ts | 10 ++- .../scenarios/02_transaction_metrics.test.ts | 11 +++- .../03_span_destination_metrics.test.ts | 24 +++++-- .../scenarios/04_breakdown_metrics.test.ts | 21 ++++--- .../05_transactions_with_errors.test.ts | 16 ++--- .../scenarios/06_application_metrics.test.ts | 4 +- .../cypress/fixtures/synthtrace/opbeans.ts | 46 ++++++++++---- .../infrastructure/generate_data.ts | 20 ++++-- .../settings/agent_configurations.spec.ts | 16 +++-- .../read_only_user/errors/generate_data.ts | 28 ++++++--- .../service_inventory/generate_data.ts | 8 ++- .../header_filters/generate_data.ts | 16 +++-- .../aws_lambda/generate_data.ts | 8 ++- .../generate_span_links_data.ts | 62 +++++++++++++++---- .../tests/alerts/anomaly_alert.spec.ts | 6 +- .../tests/anomalies/anomaly_charts.spec.ts | 12 ++-- .../generate_data.ts | 8 ++- .../tests/cold_start/generate_data.ts | 8 ++- .../tests/data_view/static.spec.ts | 10 ++- .../tests/dependencies/generate_data.ts | 8 ++- .../dependencies/generate_operation_data.ts | 18 +++--- .../tests/dependencies/top_spans.spec.ts | 30 ++++++--- .../tests/error_rate/service_apis.spec.ts | 10 +-- .../tests/error_rate/service_maps.spec.ts | 16 +++-- .../tests/errors/error_group_list.spec.ts | 20 +++--- .../tests/errors/generate_data.ts | 12 ++-- .../generate_data.ts | 10 +-- .../generate_data.ts | 12 ++-- .../tests/infrastructure/generate_data.ts | 10 +-- .../tests/latency/service_apis.spec.ts | 8 +-- .../tests/latency/service_maps.spec.ts | 11 ++-- .../observability_overview.spec.ts | 12 ++-- .../service_nodes/get_service_nodes.spec.ts | 4 +- .../instances_main_statistics.spec.ts | 26 +++++--- .../services/error_groups/generate_data.ts | 20 +++--- .../get_service_node_metadata.spec.ts | 6 +- .../services/service_details/generate_data.ts | 8 ++- .../services/service_icons/generate_data.ts | 6 +- .../sorted_and_filtered_services.spec.ts | 21 +++++-- .../tests/services/throughput.spec.ts | 12 ++-- .../tests/services/top_services.spec.ts | 18 +++--- .../tests/span_links/data_generator.ts | 26 ++++---- .../storage_explorer_timeseries_chart.spec.ts | 18 ++++-- .../throughput/dependencies_apis.spec.ts | 30 ++++++--- .../tests/throughput/service_apis.spec.ts | 8 +-- .../tests/throughput/service_maps.spec.ts | 11 ++-- .../tests/traces/find_traces.spec.ts | 18 ++++-- .../tests/traces/trace_by_id.spec.ts | 14 +++-- ...actions_groups_detailed_statistics.spec.ts | 6 +- 63 files changed, 699 insertions(+), 343 deletions(-) diff --git a/packages/kbn-apm-synthtrace/README.md b/packages/kbn-apm-synthtrace/README.md index dcd50215c6a85..4aaaeee5b672c 100644 --- a/packages/kbn-apm-synthtrace/README.md +++ b/packages/kbn-apm-synthtrace/README.md @@ -27,7 +27,7 @@ This library can currently be used in two ways: ```ts import { service, timerange, toElasticsearchOutput } from '@kbn/apm-synthtrace'; -const instance = service('synth-go', 'production', 'go').instance('instance-a'); +const instance = service({name: 'synth-go', environment: 'production', agentName: 'go'}).instance('instance-a'); const from = new Date('2021-01-01T12:00:00.000Z').getTime(); const to = new Date('2021-01-01T12:00:00.000Z').getTime(); @@ -37,7 +37,7 @@ const traceEvents = timerange(from, to) .rate(10) .flatMap((timestamp) => instance - .transaction('GET /api/product/list') + .transaction({transactionName: 'GET /api/product/list'}) .timestamp(timestamp) .duration(1000) .success() diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/browser.ts b/packages/kbn-apm-synthtrace/src/lib/apm/browser.ts index ebba6a0e89a69..89a1ac5d34a1d 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/browser.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/browser.ts @@ -12,7 +12,13 @@ import { RumSpan } from './rum_span'; import { RumTransaction } from './rum_transaction'; export class Browser extends Entity { - transaction(transactionName: string, transactionType: string = 'page-load') { + transaction({ + transactionName, + transactionType = 'page-load', + }: { + transactionName: string; + transactionType?: string; + }) { return new RumTransaction({ ...this.fields, 'transaction.name': transactionName, @@ -20,7 +26,15 @@ export class Browser extends Entity { }); } - span(spanName: string, spanType: string, spanSubtype: string) { + span({ + spanName, + spanType, + spanSubtype, + }: { + spanName: string; + spanType: string; + spanSubtype: string; + }) { return new RumSpan({ ...this.fields, 'span.name': spanName, @@ -30,11 +44,19 @@ export class Browser extends Entity { } } -export function browser(serviceName: string, production: string, userAgent: ApmUserAgentFields) { +export function browser({ + serviceName, + environment, + userAgent, +}: { + serviceName: string; + environment: string; + userAgent: ApmUserAgentFields; +}) { return new Browser({ 'agent.name': 'rum-js', 'service.name': serviceName, - 'service.environment': production, + 'service.environment': environment, ...userAgent, }); } diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts b/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts index d212c1f2cead0..32a81de9f307a 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts @@ -14,7 +14,13 @@ import { Transaction } from './transaction'; import { ApmApplicationMetricFields, ApmFields } from './apm_fields'; export class Instance extends Entity { - transaction(transactionName: string, transactionType = 'request') { + transaction({ + transactionName, + transactionType = 'request', + }: { + transactionName: string; + transactionType?: string; + }) { return new Transaction({ ...this.fields, 'transaction.name': transactionName, @@ -22,7 +28,16 @@ export class Instance extends Entity { }); } - span(spanName: string, spanType: string, spanSubtype?: string, apmFields?: ApmFields) { + span({ + spanName, + spanType, + spanSubtype, + ...apmFields + }: { + spanName: string; + spanType: string; + spanSubtype?: string; + } & ApmFields) { return new Span({ ...this.fields, ...apmFields, @@ -32,7 +47,15 @@ export class Instance extends Entity { }); } - error(message: string, type?: string, groupingName?: string) { + error({ + message, + type, + groupingName, + }: { + message: string; + type?: string; + groupingName?: string; + }) { return new ApmError({ ...this.fields, 'error.exception': [{ message, ...(type ? { type } : {}) }], diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/service.ts b/packages/kbn-apm-synthtrace/src/lib/apm/service.ts index ded149f0b6236..0939535a87135 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/service.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/service.ts @@ -20,7 +20,15 @@ export class Service extends Entity { } } -export function service(name: string, environment: string, agentName: string) { +export function service({ + name, + environment, + agentName, +}: { + name: string; + environment: string; + agentName: string; +}) { return new Service({ 'service.name': name, 'service.environment': environment, diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/span.ts b/packages/kbn-apm-synthtrace/src/lib/apm/span.ts index 388e65385e7dd..a5795ae321478 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/span.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/span.ts @@ -47,7 +47,7 @@ export function httpExitSpan({ }: { spanName: string; destinationUrl: string; -}): [string, string, string, ApmFields] { +}) { // origin: 'http://opbeans-go:3000', // host: 'opbeans-go:3000', // hostname: 'opbeans-go', @@ -57,39 +57,29 @@ export function httpExitSpan({ const spanType = 'external'; const spanSubType = 'http'; - return [ + return { spanName, spanType, spanSubType, - { - 'destination.address': destination.hostname, - 'destination.port': parseInt(destination.port, 10), - 'service.target.name': destination.host, - 'span.destination.service.name': destination.origin, - 'span.destination.service.resource': destination.host, - 'span.destination.service.type': 'external', - }, - ]; + 'destination.address': destination.hostname, + 'destination.port': parseInt(destination.port, 10), + 'service.target.name': destination.host, + 'span.destination.service.name': destination.origin, + 'span.destination.service.resource': destination.host, + 'span.destination.service.type': 'external', + }; } -export function dbExitSpan({ - spanName, - spanSubType, -}: { - spanName: string; - spanSubType?: string; -}): [string, string, string | undefined, ApmFields] { +export function dbExitSpan({ spanName, spanSubType }: { spanName: string; spanSubType?: string }) { const spanType = 'db'; - return [ + return { spanName, spanType, spanSubType, - { - 'service.target.type': spanSubType, - 'span.destination.service.name': spanSubType, - 'span.destination.service.resource': spanSubType, - 'span.destination.service.type': spanType, - }, - ]; + 'service.target.type': spanSubType, + 'span.destination.service.name': spanSubType, + 'span.destination.service.resource': spanSubType, + 'span.destination.service.type': spanType, + }; } diff --git a/packages/kbn-apm-synthtrace/src/scenarios/aws_lambda.ts b/packages/kbn-apm-synthtrace/src/scenarios/aws_lambda.ts index fa04d6e4f6465..2dcce23ab2a20 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/aws_lambda.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/aws_lambda.ts @@ -23,7 +23,9 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const range = timerange(from, to); const timestamps = range.ratePerMinute(180); - const instance = apm.service('lambda-python', ENVIRONMENT, 'python').instance('instance'); + const instance = apm + .service({ name: 'lambda-python', environment: ENVIRONMENT, agentName: 'python' }) + .instance('instance'); const traceEventsSetups = [ { functionName: 'lambda-python-1', coldStart: true }, @@ -33,7 +35,7 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const traceEvents = ({ functionName, coldStart }: typeof traceEventsSetups[0]) => { return timestamps.generator((timestamp) => instance - .transaction('GET /order/{id}') + .transaction({ transactionName: 'GET /order/{id}' }) .defaults({ 'service.runtime.name': 'AWS_Lambda_python3.8', 'cloud.provider': 'aws', diff --git a/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace.ts b/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace.ts index a87cbfe5ab4d3..7834011afa69a 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace.ts @@ -23,23 +23,27 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const transactionName = '240rpm/75% 1000ms'; const successfulTimestamps = range.interval('1s').rate(3); - const opbeansRum = apm.service('opbeans-rum', ENVIRONMENT, 'rum-js').instance('my-instance'); + const opbeansRum = apm + .service({ name: 'opbeans-rum', environment: ENVIRONMENT, agentName: 'rum-js' }) + .instance('my-instance'); const opbeansNode = apm - .service('opbeans-node', ENVIRONMENT, 'nodejs') + .service({ name: 'opbeans-node', environment: ENVIRONMENT, agentName: 'nodejs' }) + .instance('my-instance'); + const opbeansGo = apm + .service({ name: 'opbeans-go', environment: ENVIRONMENT, agentName: 'go' }) .instance('my-instance'); - const opbeansGo = apm.service('opbeans-go', ENVIRONMENT, 'go').instance('my-instance'); const traces = successfulTimestamps.generator((timestamp) => { // opbeans-rum return opbeansRum - .transaction(transactionName) + .transaction({ transactionName }) .duration(400) .timestamp(timestamp) .children( // opbeans-rum -> opbeans-node opbeansRum .span( - ...httpExitSpan({ + httpExitSpan({ spanName: 'GET /api/products/top', destinationUrl: 'http://opbeans-node:3000', }) @@ -50,14 +54,14 @@ const scenario: Scenario = async (runOptions: RunOptions) => { .children( // opbeans-node opbeansNode - .transaction('Initial transaction in opbeans-node') + .transaction({ transactionName: 'Initial transaction in opbeans-node' }) .duration(300) .timestamp(timestamp) .children( opbeansNode // opbeans-node -> opbeans-go .span( - ...httpExitSpan({ + httpExitSpan({ spanName: 'GET opbeans-go:3000', destinationUrl: 'http://opbeans-go:3000', }) @@ -69,12 +73,12 @@ const scenario: Scenario = async (runOptions: RunOptions) => { // opbeans-go opbeansGo - .transaction('Initial transaction in opbeans-go') + .transaction({ transactionName: 'Initial transaction in opbeans-go' }) .timestamp(timestamp) .duration(200) .children( opbeansGo - .span('custom_operation', 'custom') + .span({ spanName: 'custom_operation', spanType: 'custom' }) .timestamp(timestamp) .duration(100) .success() diff --git a/packages/kbn-apm-synthtrace/src/scenarios/high_throughput.ts b/packages/kbn-apm-synthtrace/src/scenarios/high_throughput.ts index 41b21df2e83e1..1b2ec3d64b087 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/high_throughput.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/high_throughput.ts @@ -28,11 +28,11 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const instances = services.map((service, index) => apm - .service( - `${service}-${languages[index % languages.length]}`, - 'production', - languages[index % languages.length] - ) + .service({ + name: `${service}-${languages[index % languages.length]}`, + environment: 'production', + agentName: languages[index % languages.length], + }) .instance(`instance-${index}`) ); const entities = [ @@ -68,18 +68,22 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const generateError = index % random(mod, 9) === 0; const generateChildError = index % random(mod, 9) === 0; const span = instance - .transaction(url) + .transaction({ transactionName: url }) .timestamp(timestamp) .duration(duration) .children( instance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .duration(childDuration) .destination('elasticsearch') .timestamp(timestamp) .outcome(generateError && generateChildError ? 'failure' : 'success'), instance - .span('custom_operation', 'custom') + .span({ spanName: 'custom_operation', spanType: 'custom' }) .duration(remainderDuration) .success() .timestamp(timestamp + childDuration) @@ -88,7 +92,9 @@ const scenario: Scenario = async (runOptions: RunOptions) => { ? span.success() : span .failure() - .errors(instance.error(`No handler for ${url}`).timestamp(timestamp + 50)); + .errors( + instance.error({ message: `No handler for ${url}` }).timestamp(timestamp + 50) + ); }); return successfulTraceEvents; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/low_throughput.ts b/packages/kbn-apm-synthtrace/src/scenarios/low_throughput.ts index d842a0650b423..006a5074f7c1b 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/low_throughput.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/low_throughput.ts @@ -32,11 +32,13 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const instances = services.map((service, index) => apm - .service( - `${services[index % services.length]}-${languages[index % languages.length]}-${index}`, - ENVIRONMENT, - languages[index % languages.length] - ) + .service({ + name: `${services[index % services.length]}-${ + languages[index % languages.length] + }-${index}`, + environment: ENVIRONMENT, + agentName: languages[index % languages.length], + }) .instance(`instance-${index}`) ); @@ -53,18 +55,22 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const generateError = index % random(mod, 9) === 0; const generateChildError = index % random(mod, 9) === 0; const span = instance - .transaction(url) + .transaction({ transactionName: url }) .timestamp(timestamp) .duration(duration) .children( instance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .duration(childDuration) .destination('elasticsearch') .timestamp(timestamp) .outcome(generateError && generateChildError ? 'failure' : 'success'), instance - .span('custom_operation', 'custom') + .span({ spanName: 'custom_operation', spanType: 'custom' }) .duration(remainderDuration) .success() .timestamp(timestamp + childDuration) @@ -73,7 +79,9 @@ const scenario: Scenario = async (runOptions: RunOptions) => { ? span.success() : span .failure() - .errors(instance.error(`No handler for ${url}`).timestamp(timestamp + 50)); + .errors( + instance.error({ message: `No handler for ${url}` }).timestamp(timestamp + 50) + ); }); return successfulTraceEvents; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts b/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts index 501d0e678f0f4..1829d46e17232 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts @@ -33,11 +33,13 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const instances = [...Array(numServices).keys()].map((index) => apm - .service( - `${services[index % services.length]}-${languages[index % languages.length]}-${index}`, - ENVIRONMENT, - languages[index % languages.length] - ) + .service({ + name: `${services[index % services.length]}-${ + languages[index % languages.length] + }-${index}`, + environment: ENVIRONMENT, + agentName: languages[index % languages.length], + }) .instance(`instance-${index}`) ); @@ -53,18 +55,22 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const generateError = random(1, 4) % 3 === 0; const generateChildError = random(0, 5) % 2 === 0; const span = instance - .transaction(url) + .transaction({ transactionName: url }) .timestamp(timestamp) .duration(duration) .children( instance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .duration(childDuration) .destination('elasticsearch') .timestamp(timestamp) .outcome(generateError && generateChildError ? 'failure' : 'success'), instance - .span('custom_operation', 'custom') + .span({ spanName: 'custom_operation', spanType: 'custom' }) .duration(remainderDuration) .success() .timestamp(timestamp + childDuration) @@ -73,7 +79,9 @@ const scenario: Scenario = async (runOptions: RunOptions) => { ? span.success() : span .failure() - .errors(instance.error(`No handler for ${url}`).timestamp(timestamp + 50)); + .errors( + instance.error({ message: `No handler for ${url}` }).timestamp(timestamp + 50) + ); }); return successfulTraceEvents; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/simple_trace.ts b/packages/kbn-apm-synthtrace/src/scenarios/simple_trace.ts index f8444ab6e5879..8f3c84564787c 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/simple_trace.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/simple_trace.ts @@ -31,24 +31,30 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const failedTimestamps = range.ratePerMinute(180); const instances = [...Array(numServices).keys()].map((index) => - apm.service(`opbeans-go-${index}`, ENVIRONMENT, 'go').instance('instance') + apm + .service({ name: `opbeans-go-${index}`, environment: ENVIRONMENT, agentName: 'go' }) + .instance('instance') ); const instanceSpans = (instance: Instance) => { const successfulTraceEvents = successfulTimestamps.generator((timestamp) => instance - .transaction(transactionName) + .transaction({ transactionName }) .timestamp(timestamp) .duration(1000) .success() .children( instance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .duration(1000) .success() .destination('elasticsearch') .timestamp(timestamp), instance - .span('custom_operation', 'custom') + .span({ spanName: 'custom_operation', spanType: 'custom' }) .duration(100) .success() .timestamp(timestamp) @@ -57,12 +63,14 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const failedTraceEvents = failedTimestamps.generator((timestamp) => instance - .transaction(transactionName) + .transaction({ transactionName }) .timestamp(timestamp) .duration(1000) .failure() .errors( - instance.error('[ResponseError] index_not_found_exception').timestamp(timestamp + 50) + instance + .error({ message: '[ResponseError] index_not_found_exception' }) + .timestamp(timestamp + 50) ) ); diff --git a/packages/kbn-apm-synthtrace/src/scenarios/span_links.ts b/packages/kbn-apm-synthtrace/src/scenarios/span_links.ts index 0c9250dcd16f7..4267b6465b179 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/span_links.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/span_links.ts @@ -35,7 +35,7 @@ const scenario: Scenario = async () => { generate: ({ from, to }) => { const producerInternalOnlyInstance = apm - .service('producer-internal-only', ENVIRONMENT, 'go') + .service({ name: 'producer-internal-only', environment: ENVIRONMENT, agentName: 'go' }) .instance('instance-a'); const producerInternalOnlyEvents = timerange( new Date('2022-04-25T19:00:00.000Z'), @@ -45,13 +45,13 @@ const scenario: Scenario = async () => { .rate(1) .generator((timestamp) => { return producerInternalOnlyInstance - .transaction('Transaction A') + .transaction({ transactionName: 'Transaction A' }) .timestamp(timestamp) .duration(1000) .success() .children( producerInternalOnlyInstance - .span('Span A', 'custom') + .span({ spanName: 'Span A', spanType: 'custom' }) .timestamp(timestamp + 50) .duration(100) .success() @@ -62,20 +62,20 @@ const scenario: Scenario = async () => { const spanASpanLink = getSpanLinksFromEvents(producerInternalOnlyApmFields); const producerConsumerInstance = apm - .service('producer-consumer', ENVIRONMENT, 'java') + .service({ name: 'producer-consumer', environment: ENVIRONMENT, agentName: 'java' }) .instance('instance-b'); const producerConsumerEvents = timerange(from, to) .interval('1m') .rate(1) .generator((timestamp) => { return producerConsumerInstance - .transaction('Transaction B') + .transaction({ transactionName: 'Transaction B' }) .timestamp(timestamp) .duration(1000) .success() .children( producerConsumerInstance - .span('Span B', 'external') + .span({ spanName: 'Span B', spanType: 'external' }) .defaults({ 'span.links': shuffle([...generateExternalSpanLinks(), ...spanASpanLink]), }) @@ -88,19 +88,21 @@ const scenario: Scenario = async () => { const producerConsumerApmFields = producerConsumerEvents.toArray(); const spanBSpanLink = getSpanLinksFromEvents(producerConsumerApmFields); - const consumerInstance = apm.service('consumer', ENVIRONMENT, 'ruby').instance('instance-c'); + const consumerInstance = apm + .service({ name: 'consumer', environment: ENVIRONMENT, agentName: 'ruby' }) + .instance('instance-c'); const consumerEvents = timerange(from, to) .interval('1m') .rate(1) .generator((timestamp) => { return consumerInstance - .transaction('Transaction C') + .transaction({ transactionName: 'Transaction C' }) .timestamp(timestamp) .duration(1000) .success() .children( consumerInstance - .span('Span C', 'external') + .span({ spanName: 'Span C', spanType: 'external' }) .defaults({ 'span.links': spanBSpanLink }) .timestamp(timestamp + 50) .duration(900) diff --git a/packages/kbn-apm-synthtrace/src/test/event_dsl_behavior.test.ts b/packages/kbn-apm-synthtrace/src/test/event_dsl_behavior.test.ts index 31a5ed02429d5..3cf1d8500e12d 100644 --- a/packages/kbn-apm-synthtrace/src/test/event_dsl_behavior.test.ts +++ b/packages/kbn-apm-synthtrace/src/test/event_dsl_behavior.test.ts @@ -19,7 +19,11 @@ describe('DSL invocations', () => { new Date('2021-01-01T00:00:00.000Z'), new Date('2021-01-01T00:15:00.000Z') ); - const javaService = apm.service('opbeans-java', 'production', 'java'); + const javaService = apm.service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }); const javaInstance = javaService.instance('instance-1'); let globalSeq = 0; @@ -28,13 +32,13 @@ describe('DSL invocations', () => { .rate(1) .generator((timestamp, index) => javaInstance - .transaction(`GET /api/product/${index}/${globalSeq++}`) + .transaction({ transactionName: `GET /api/product/${index}/${globalSeq++}` }) .duration(1000) .success() .timestamp(timestamp) .children( javaInstance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ spanName: 'GET apm-*/_search', spanType: 'db', spanSubtype: 'elasticsearch' }) .success() .duration(900) .timestamp(timestamp + 50) diff --git a/packages/kbn-apm-synthtrace/src/test/rate_per_minute.test.ts b/packages/kbn-apm-synthtrace/src/test/rate_per_minute.test.ts index e40848ab9f47b..5a100aa8a404d 100644 --- a/packages/kbn-apm-synthtrace/src/test/rate_per_minute.test.ts +++ b/packages/kbn-apm-synthtrace/src/test/rate_per_minute.test.ts @@ -19,7 +19,11 @@ describe('rate per minute calculations', () => { let events: Array>; beforeEach(() => { - const javaService = apm.service('opbeans-java', 'production', 'java'); + const javaService = apm.service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }); const javaInstance = javaService.instance('instance-1'); iterable = range @@ -27,13 +31,13 @@ describe('rate per minute calculations', () => { .rate(1) .generator((timestamp) => javaInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .duration(1000) .success() .timestamp(timestamp) .children( javaInstance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ spanName: 'GET apm-*/_search', spanType: 'db', spanSubtype: 'elasticsearch' }) .success() .duration(900) .timestamp(timestamp + 50) diff --git a/packages/kbn-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts b/packages/kbn-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts index 8f405700fa2c1..a278997ecdf73 100644 --- a/packages/kbn-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts +++ b/packages/kbn-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts @@ -16,7 +16,11 @@ describe('simple trace', () => { let events: Array>; beforeEach(() => { - const javaService = apm.service('opbeans-java', 'production', 'java'); + const javaService = apm.service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }); const javaInstance = javaService.instance('instance-1').containerId('instance-1'); const range = timerange( @@ -29,13 +33,13 @@ describe('simple trace', () => { .rate(1) .generator((timestamp) => javaInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .duration(1000) .success() .timestamp(timestamp) .children( javaInstance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ spanName: 'GET apm-*/_search', spanType: 'db', spanSubtype: 'elasticsearch' }) .success() .duration(900) .timestamp(timestamp + 50) diff --git a/packages/kbn-apm-synthtrace/src/test/scenarios/02_transaction_metrics.test.ts b/packages/kbn-apm-synthtrace/src/test/scenarios/02_transaction_metrics.test.ts index d7f60b2396cd0..99715ab6998d6 100644 --- a/packages/kbn-apm-synthtrace/src/test/scenarios/02_transaction_metrics.test.ts +++ b/packages/kbn-apm-synthtrace/src/test/scenarios/02_transaction_metrics.test.ts @@ -16,7 +16,11 @@ describe('transaction metrics', () => { let events: Array>; beforeEach(() => { - const javaService = apm.service('opbeans-java', 'production', 'java'); + const javaService = apm.service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }); const javaInstance = javaService.instance('instance-1'); const range = timerange( @@ -25,7 +29,10 @@ describe('transaction metrics', () => { ); const span = (timestamp: number) => - javaInstance.transaction('GET /api/product/list').duration(1000).timestamp(timestamp); + javaInstance + .transaction({ transactionName: 'GET /api/product/list' }) + .duration(1000) + .timestamp(timestamp); const processor = new StreamProcessor({ processors: [getTransactionMetrics], diff --git a/packages/kbn-apm-synthtrace/src/test/scenarios/03_span_destination_metrics.test.ts b/packages/kbn-apm-synthtrace/src/test/scenarios/03_span_destination_metrics.test.ts index 39c33e6486f69..f5c721221c328 100644 --- a/packages/kbn-apm-synthtrace/src/test/scenarios/03_span_destination_metrics.test.ts +++ b/packages/kbn-apm-synthtrace/src/test/scenarios/03_span_destination_metrics.test.ts @@ -16,7 +16,11 @@ describe('span destination metrics', () => { let events: Array>; beforeEach(() => { - const javaService = apm.service('opbeans-java', 'production', 'java'); + const javaService = apm.service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }); const javaInstance = javaService.instance('instance-1'); const range = timerange( @@ -31,13 +35,17 @@ describe('span destination metrics', () => { .rate(25) .generator((timestamp) => javaInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .duration(1000) .success() .timestamp(timestamp) .children( javaInstance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .timestamp(timestamp) .duration(1000) .destination('elasticsearch') @@ -49,19 +57,23 @@ describe('span destination metrics', () => { .rate(50) .generator((timestamp) => javaInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .duration(1000) .failure() .timestamp(timestamp) .children( javaInstance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .timestamp(timestamp) .duration(1000) .destination('elasticsearch') .failure(), javaInstance - .span('custom_operation', 'app') + .span({ spanName: 'custom_operation', spanType: 'app' }) .timestamp(timestamp) .duration(500) .success() diff --git a/packages/kbn-apm-synthtrace/src/test/scenarios/04_breakdown_metrics.test.ts b/packages/kbn-apm-synthtrace/src/test/scenarios/04_breakdown_metrics.test.ts index 831ca790e57a6..731dea453058d 100644 --- a/packages/kbn-apm-synthtrace/src/test/scenarios/04_breakdown_metrics.test.ts +++ b/packages/kbn-apm-synthtrace/src/test/scenarios/04_breakdown_metrics.test.ts @@ -22,7 +22,11 @@ describe('breakdown metrics', () => { const INTERVALS = 6; beforeEach(() => { - const javaService = apm.service('opbeans-java', 'production', 'java'); + const javaService = apm.service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }); const javaInstance = javaService.instance('instance-1'); const start = new Date('2021-01-01T00:00:00.000Z'); @@ -34,15 +38,18 @@ describe('breakdown metrics', () => { .rate(LIST_RATE) .generator((timestamp) => javaInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .timestamp(timestamp) .duration(1000) .children( javaInstance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ spanName: 'GET apm-*/_search', spanType: 'db', spanSubtype: 'elasticsearch' }) .timestamp(timestamp + 150) .duration(500), - javaInstance.span('GET foo', 'db', 'redis').timestamp(timestamp).duration(100) + javaInstance + .span({ spanName: 'GET foo', spanType: 'db', spanSubtype: 'redis' }) + .timestamp(timestamp) + .duration(100) ) ); @@ -51,17 +58,17 @@ describe('breakdown metrics', () => { .rate(ID_RATE) .generator((timestamp) => javaInstance - .transaction('GET /api/product/:id') + .transaction({ transactionName: 'GET /api/product/:id' }) .timestamp(timestamp) .duration(1000) .children( javaInstance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ spanName: 'GET apm-*/_search', spanType: 'db', spanSubtype: 'elasticsearch' }) .duration(500) .timestamp(timestamp + 100) .children( javaInstance - .span('bar', 'external', 'http') + .span({ spanName: 'bar', spanType: 'external', spanSubtype: 'http' }) .timestamp(timestamp + 200) .duration(100) ) diff --git a/packages/kbn-apm-synthtrace/src/test/scenarios/05_transactions_with_errors.test.ts b/packages/kbn-apm-synthtrace/src/test/scenarios/05_transactions_with_errors.test.ts index b9b12aeab0754..305c3ed2d88a4 100644 --- a/packages/kbn-apm-synthtrace/src/test/scenarios/05_transactions_with_errors.test.ts +++ b/packages/kbn-apm-synthtrace/src/test/scenarios/05_transactions_with_errors.test.ts @@ -14,13 +14,15 @@ describe('transactions with errors', () => { const timestamp = new Date('2021-01-01T00:00:00.000Z').getTime(); beforeEach(() => { - instance = apm.service('opbeans-java', 'production', 'java').instance('instance'); + instance = apm + .service({ name: 'opbeans-java', environment: 'production', agentName: 'java' }) + .instance('instance'); }); it('generates error events', () => { const events = instance - .transaction('GET /api') + .transaction({ transactionName: 'GET /api' }) .timestamp(timestamp) - .errors(instance.error('test error').timestamp(timestamp)) + .errors(instance.error({ message: 'test error' }).timestamp(timestamp)) .serialize(); const errorEvents = events.filter((event) => event['processor.event'] === 'error'); @@ -39,9 +41,9 @@ describe('transactions with errors', () => { it('sets the transaction and trace id', () => { const [transaction, error] = instance - .transaction('GET /api') + .transaction({ transactionName: 'GET /api' }) .timestamp(timestamp) - .errors(instance.error('test error').timestamp(timestamp)) + .errors(instance.error({ message: 'test error' }).timestamp(timestamp)) .serialize(); const keys = ['transaction.id', 'trace.id', 'transaction.type']; @@ -55,9 +57,9 @@ describe('transactions with errors', () => { it('sets the error grouping key', () => { const [, error] = instance - .transaction('GET /api') + .transaction({ transactionName: 'GET /api' }) .timestamp(timestamp) - .errors(instance.error('test error').timestamp(timestamp)) + .errors(instance.error({ message: 'test error' }).timestamp(timestamp)) .serialize(); expect(error['error.grouping_name']).toEqual('test error'); diff --git a/packages/kbn-apm-synthtrace/src/test/scenarios/06_application_metrics.test.ts b/packages/kbn-apm-synthtrace/src/test/scenarios/06_application_metrics.test.ts index 7bae1e51f1ab3..c9f33c2f23711 100644 --- a/packages/kbn-apm-synthtrace/src/test/scenarios/06_application_metrics.test.ts +++ b/packages/kbn-apm-synthtrace/src/test/scenarios/06_application_metrics.test.ts @@ -14,7 +14,9 @@ describe('application metrics', () => { const timestamp = new Date('2021-01-01T00:00:00.000Z').getTime(); beforeEach(() => { - instance = apm.service('opbeans-java', 'production', 'java').instance('instance'); + instance = apm + .service({ name: 'opbeans-java', environment: 'production', agentName: 'java' }) + .instance('instance'); }); it('generates application metricsets', () => { const events = instance diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts b/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts index 1be9873d25c4f..bf8802c39f9f8 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts @@ -10,50 +10,70 @@ export function opbeans({ from, to }: { from: number; to: number }) { const range = timerange(from, to); const opbeansJava = apm - .service('opbeans-java', 'production', 'java') + .service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }) .instance('opbeans-java-prod-1') .podId('opbeans-java-prod-1-pod'); const opbeansNode = apm - .service('opbeans-node', 'production', 'nodejs') + .service({ + name: 'opbeans-node', + environment: 'production', + agentName: 'nodejs', + }) .instance('opbeans-node-prod-1'); - const opbeansRum = apm.browser( - 'opbeans-rum', - 'production', - apm.getChromeUserAgentDefaults() - ); + const opbeansRum = apm.browser({ + serviceName: 'opbeans-rum', + environment: 'production', + userAgent: apm.getChromeUserAgentDefaults(), + }); return range .interval('1s') .rate(1) .generator((timestamp) => [ opbeansJava - .transaction('GET /api/product') + .transaction({ transactionName: 'GET /api/product' }) .timestamp(timestamp) .duration(1000) .success() .errors( - opbeansJava.error('[MockError] Foo', `Exception`).timestamp(timestamp) + opbeansJava + .error({ message: '[MockError] Foo', type: `Exception` }) + .timestamp(timestamp) ) .children( opbeansJava - .span('SELECT * FROM product', 'db', 'postgresql') + .span({ + spanName: 'SELECT * FROM product', + spanType: 'db', + spanSubtype: 'postgresql', + }) .timestamp(timestamp) .duration(50) .success() .destination('postgresql') ), opbeansNode - .transaction('GET /api/product/:id') + .transaction({ transactionName: 'GET /api/product/:id' }) .timestamp(timestamp) .duration(500) .success(), opbeansNode - .transaction('Worker job', 'Worker') + .transaction({ + transactionName: 'Worker job', + transactionType: 'Worker', + }) .timestamp(timestamp) .duration(1000) .success(), - opbeansRum.transaction('/').timestamp(timestamp).duration(1000), + opbeansRum + .transaction({ transactionName: '/' }) + .timestamp(timestamp) + .duration(1000), ]); } diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/infrastructure/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/infrastructure/generate_data.ts index 52cf6b988f1a1..dde70238377a7 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/infrastructure/generate_data.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/infrastructure/generate_data.ts @@ -9,21 +9,29 @@ import { apm, timerange } from '@kbn/apm-synthtrace'; export function generateData({ from, to }: { from: number; to: number }) { const range = timerange(from, to); const serviceRunsInContainerInstance = apm - .service('synth-go', 'production', 'go') + .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) .instance('instance-a'); const serviceInstance = apm - .service('synth-java', 'production', 'java') + .service({ + name: 'synth-java', + environment: 'production', + agentName: 'java', + }) .instance('instance-b'); const serviceNoInfraDataInstance = apm - .service('synth-node', 'production', 'node') + .service({ + name: 'synth-node', + environment: 'production', + agentName: 'node', + }) .instance('instance-b'); return range.interval('1m').generator((timestamp) => { return [ serviceRunsInContainerInstance - .transaction('GET /apple 🍎') + .transaction({ transactionName: 'GET /apple 🍎' }) .defaults({ 'container.id': 'foo', 'host.hostname': 'bar', @@ -33,7 +41,7 @@ export function generateData({ from, to }: { from: number; to: number }) { .duration(1000) .success(), serviceInstance - .transaction('GET /banana 🍌') + .transaction({ transactionName: 'GET /banana 🍌' }) .defaults({ 'host.hostname': 'bar', }) @@ -41,7 +49,7 @@ export function generateData({ from, to }: { from: number; to: number }) { .duration(1000) .success(), serviceNoInfraDataInstance - .transaction('GET /banana 🍌') + .transaction({ transactionName: 'GET /banana 🍌' }) .timestamp(timestamp) .duration(1000) .success(), diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/agent_configurations.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/agent_configurations.spec.ts index 23154492c9f44..5be39b4f082dc 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/agent_configurations.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/agent_configurations.spec.ts @@ -29,12 +29,20 @@ function generateData({ const range = timerange(from, to); const service1 = apm - .service(serviceName, 'production', 'java') + .service({ + name: serviceName, + environment: 'production', + agentName: 'java', + }) .instance('service-1-prod-1') .podId('service-1-prod-1-pod'); const service2 = apm - .service(serviceName, 'development', 'nodejs') + .service({ + name: serviceName, + environment: 'development', + agentName: 'nodejs', + }) .instance('opbeans-node-prod-1'); return range @@ -42,12 +50,12 @@ function generateData({ .rate(1) .generator((timestamp, index) => [ service1 - .transaction('GET /apple 🍎 ') + .transaction({ transactionName: 'GET /apple 🍎 ' }) .timestamp(timestamp) .duration(1000) .success(), service2 - .transaction('GET /banana 🍌') + .transaction({ transactionName: 'GET /banana 🍌' }) .timestamp(timestamp) .duration(500) .success(), diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/generate_data.ts index 56978f03123a8..8f432305f2ba9 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/generate_data.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/generate_data.ts @@ -10,12 +10,20 @@ export function generateData({ from, to }: { from: number; to: number }) { const range = timerange(from, to); const opbeansJava = apm - .service('opbeans-java', 'production', 'java') + .service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }) .instance('opbeans-java-prod-1') .podId('opbeans-java-prod-1-pod'); const opbeansNode = apm - .service('opbeans-node', 'production', 'nodejs') + .service({ + name: 'opbeans-node', + environment: 'production', + agentName: 'nodejs', + }) .instance('opbeans-node-prod-1'); return range @@ -23,17 +31,17 @@ export function generateData({ from, to }: { from: number; to: number }) { .rate(1) .generator((timestamp, index) => [ opbeansJava - .transaction('GET /apple 🍎 ') + .transaction({ transactionName: 'GET /apple 🍎 ' }) .timestamp(timestamp) .duration(1000) .success() .errors( opbeansJava - .error(`Error ${index}`, `exception ${index}`) + .error({ message: `Error ${index}`, type: `exception ${index}` }) .timestamp(timestamp) ), opbeansNode - .transaction('GET /banana 🍌') + .transaction({ transactionName: 'GET /banana 🍌' }) .timestamp(timestamp) .duration(500) .success(), @@ -52,7 +60,11 @@ export function generateErrors({ const range = timerange(from, to); const opbeansJava = apm - .service('opbeans-java', 'production', 'java') + .service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }) .instance('opbeans-java-prod-1') .podId('opbeans-java-prod-1-pod'); @@ -61,7 +73,7 @@ export function generateErrors({ .rate(1) .generator((timestamp, index) => [ opbeansJava - .transaction('GET /apple 🍎 ') + .transaction({ transactionName: 'GET /apple 🍎 ' }) .timestamp(timestamp) .duration(1000) .success() @@ -70,7 +82,7 @@ export function generateErrors({ .fill(0) .map((_, idx) => { return opbeansJava - .error(`Error ${idx}`, `exception ${idx}`) + .error({ message: `Error ${idx}`, type: `exception ${idx}` }) .timestamp(timestamp); }) ), diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/generate_data.ts index e3cdf7e8bbce8..3fd41b8a06fd0 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/generate_data.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/generate_data.ts @@ -19,7 +19,11 @@ export function generateMultipleServicesData({ .fill(0) .map((_, idx) => apm - .service(`${idx}`, 'production', 'nodejs') + .service({ + name: `${idx}`, + environment: 'production', + agentName: 'nodejs', + }) .instance('opbeans-node-prod-1') ); @@ -29,7 +33,7 @@ export function generateMultipleServicesData({ .generator((timestamp, index) => services.map((service) => service - .transaction('GET /foo') + .transaction({ transactionName: 'GET /foo' }) .timestamp(timestamp) .duration(500) .success() diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/generate_data.ts index 243f1df257a4f..6467768f75e28 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/generate_data.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/generate_data.ts @@ -18,12 +18,20 @@ export function generateData({ const range = timerange(from, to); const service1 = apm - .service(specialServiceName, 'production', 'java') + .service({ + name: specialServiceName, + environment: 'production', + agentName: 'java', + }) .instance('service-1-prod-1') .podId('service-1-prod-1-pod'); const opbeansNode = apm - .service('opbeans-node', 'production', 'nodejs') + .service({ + name: 'opbeans-node', + environment: 'production', + agentName: 'nodejs', + }) .instance('opbeans-node-prod-1'); return range @@ -31,12 +39,12 @@ export function generateData({ .rate(1) .generator((timestamp) => [ service1 - .transaction('GET /apple 🍎 ') + .transaction({ transactionName: 'GET /apple 🍎 ' }) .timestamp(timestamp) .duration(1000) .success(), opbeansNode - .transaction('GET /banana 🍌') + .transaction({ transactionName: 'GET /banana 🍌' }) .timestamp(timestamp) .duration(500) .success(), diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/aws_lambda/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/aws_lambda/generate_data.ts index bbd7553d1fa33..81d6aabf38165 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/aws_lambda/generate_data.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/aws_lambda/generate_data.ts @@ -18,7 +18,11 @@ const dataConfig = { export function generateData({ start, end }: { start: number; end: number }) { const { rate, transaction, serviceName } = dataConfig; const instance = apm - .service(serviceName, 'production', 'python') + .service({ + name: serviceName, + environment: 'production', + agentName: 'python', + }) .instance('instance-a'); const traceEvents = timerange(start, end) @@ -26,7 +30,7 @@ export function generateData({ start, end }: { start: number; end: number }) { .rate(rate) .generator((timestamp) => instance - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .defaults({ 'service.runtime.name': 'AWS_Lambda_python3.8', 'faas.coldstart': true, diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transaction_details/generate_span_links_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transaction_details/generate_span_links_data.ts index 9fd2cfe6eab0b..d623ea664bc53 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transaction_details/generate_span_links_data.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transaction_details/generate_span_links_data.ts @@ -10,7 +10,11 @@ import { SpanLink } from '../../../../../typings/es_schemas/raw/fields/span_link function getProducerInternalOnly() { const producerInternalOnlyInstance = apm - .service('producer-internal-only', 'production', 'go') + .service({ + name: 'producer-internal-only', + environment: 'production', + agentName: 'go', + }) .instance('instance a'); const events = timerange( @@ -21,13 +25,17 @@ function getProducerInternalOnly() { .rate(1) .generator((timestamp) => { return producerInternalOnlyInstance - .transaction(`Transaction A`) + .transaction({ transactionName: `Transaction A` }) .timestamp(timestamp) .duration(1000) .success() .children( producerInternalOnlyInstance - .span(`Span A`, 'external', 'http') + .span({ + spanName: `Span A`, + spanType: 'external', + spanSubtype: 'http', + }) .timestamp(timestamp + 50) .duration(100) .success() @@ -61,7 +69,11 @@ function getProducerInternalOnly() { function getProducerExternalOnly() { const producerExternalOnlyInstance = apm - .service('producer-external-only', 'production', 'java') + .service({ + name: 'producer-external-only', + environment: 'production', + agentName: 'java', + }) .instance('instance b'); const events = timerange( @@ -72,13 +84,17 @@ function getProducerExternalOnly() { .rate(1) .generator((timestamp) => { return producerExternalOnlyInstance - .transaction(`Transaction B`) + .transaction({ transactionName: `Transaction B` }) .timestamp(timestamp) .duration(1000) .success() .children( producerExternalOnlyInstance - .span(`Span B`, 'external', 'http') + .span({ + spanName: `Span B`, + spanType: 'external', + spanSubtype: 'http', + }) .defaults({ 'span.links': [ { trace: { id: 'trace#1' }, span: { id: 'span#1' } }, @@ -88,7 +104,11 @@ function getProducerExternalOnly() { .duration(100) .success(), producerExternalOnlyInstance - .span(`Span B.1`, 'external', 'http') + .span({ + spanName: `Span B.1`, + spanType: 'external', + spanSubtype: 'http', + }) .timestamp(timestamp + 50) .duration(100) .success() @@ -132,7 +152,11 @@ function getProducerConsumer({ producerInternalOnlySpanASpanLink?: SpanLink; }) { const producerConsumerInstance = apm - .service('producer-consumer', 'production', 'ruby') + .service({ + name: 'producer-consumer', + environment: 'production', + agentName: 'ruby', + }) .instance('instance c'); const events = timerange( @@ -143,7 +167,7 @@ function getProducerConsumer({ .rate(1) .generator((timestamp) => { return producerConsumerInstance - .transaction(`Transaction C`) + .transaction({ transactionName: `Transaction C` }) .defaults({ 'span.links': producerInternalOnlySpanASpanLink ? [producerInternalOnlySpanASpanLink] @@ -154,7 +178,11 @@ function getProducerConsumer({ .success() .children( producerConsumerInstance - .span(`Span C`, 'external', 'http') + .span({ + spanName: `Span C`, + spanType: 'external', + spanSubtype: 'http', + }) .timestamp(timestamp + 50) .duration(100) .success() @@ -209,7 +237,11 @@ function getConsumerMultiple({ producerConsumerTransactionCSpanLink?: SpanLink; }) { const consumerMultipleInstance = apm - .service('consumer-multiple', 'production', 'nodejs') + .service({ + name: 'consumer-multiple', + environment: 'production', + agentName: 'nodejs', + }) .instance('instance d'); const events = timerange( @@ -220,7 +252,7 @@ function getConsumerMultiple({ .rate(1) .generator((timestamp) => { return consumerMultipleInstance - .transaction(`Transaction D`) + .transaction({ transactionName: `Transaction D` }) .defaults({ 'span.links': producerInternalOnlySpanASpanLink && producerConsumerSpanCSpanLink @@ -235,7 +267,11 @@ function getConsumerMultiple({ .success() .children( consumerMultipleInstance - .span(`Span E`, 'external', 'http') + .span({ + spanName: `Span E`, + spanType: 'external', + spanSubtype: 'http', + }) .defaults({ 'span.links': producerExternalOnlySpanBSpanLink && diff --git a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts index 43ba70cc100a1..53c5fec2bd5bf 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts @@ -38,7 +38,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { let ruleId: string | undefined; before(async () => { - const serviceA = apm.service('a', 'production', 'java').instance('a'); + const serviceA = apm + .service({ name: 'a', environment: 'production', agentName: 'java' }) + .instance('a'); const events = timerange(new Date(start).getTime(), new Date(end).getTime()) .interval('1m') @@ -52,7 +54,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { return [ ...range(0, count).flatMap((_) => serviceA - .transaction('tx', 'request') + .transaction({ transactionName: 'tx' }) .timestamp(timestamp) .duration(duration) .outcome(outcome) diff --git a/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts b/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts index 0416a0124627a..80a5474ae41b2 100644 --- a/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts +++ b/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts @@ -101,9 +101,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { const NORMAL_RATE = 1; before(async () => { - const serviceA = apm.service('a', 'production', 'java').instance('a'); + const serviceA = apm + .service({ name: 'a', environment: 'production', agentName: 'java' }) + .instance('a'); - const serviceB = apm.service('b', 'development', 'go').instance('b'); + const serviceB = apm + .service({ name: 'b', environment: 'development', agentName: 'go' }) + .instance('b'); const events = timerange(new Date(start).getTime(), new Date(end).getTime()) .interval('1m') @@ -117,13 +121,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { return [ ...range(0, count).flatMap((_) => serviceA - .transaction('tx', 'request') + .transaction({ transactionName: 'tx', transactionType: 'request' }) .timestamp(timestamp) .duration(duration) .outcome(outcome) ), serviceB - .transaction('tx', 'Worker') + .transaction({ transactionName: 'tx', transactionType: 'Worker' }) .timestamp(timestamp) .duration(duration) .success(), diff --git a/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/generate_data.ts b/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/generate_data.ts index 4effd2ed23d7c..51b3a198667b0 100644 --- a/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/cold_start/cold_start_by_transaction_name/generate_data.ts @@ -27,14 +27,16 @@ export async function generateData({ warmStartRate: number; }) { const { transactionName, duration, serviceName } = dataConfig; - const instance = apm.service(serviceName, 'production', 'go').instance('instance-a'); + const instance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .instance('instance-a'); const traceEvents = timerange(start, end) .interval('1m') .rate(coldStartRate) .generator((timestamp) => instance - .transaction(transactionName) + .transaction({ transactionName }) .defaults({ 'faas.coldstart': true, }) @@ -48,7 +50,7 @@ export async function generateData({ .rate(warmStartRate) .generator((timestamp) => instance - .transaction(transactionName) + .transaction({ transactionName }) .defaults({ 'faas.coldstart': false, }) diff --git a/x-pack/test/apm_api_integration/tests/cold_start/generate_data.ts b/x-pack/test/apm_api_integration/tests/cold_start/generate_data.ts index 4baa9fd877f10..b7169d3aedc54 100644 --- a/x-pack/test/apm_api_integration/tests/cold_start/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/cold_start/generate_data.ts @@ -33,7 +33,9 @@ export async function generateData({ warmStartRate: number; }) { const { coldStartTransaction, warmStartTransaction, serviceName } = dataConfig; - const instance = apm.service(serviceName, 'production', 'go').instance('instance-a'); + const instance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .instance('instance-a'); const traceEvents = [ timerange(start, end) @@ -41,7 +43,7 @@ export async function generateData({ .rate(coldStartRate) .generator((timestamp) => instance - .transaction(coldStartTransaction.name) + .transaction({ transactionName: coldStartTransaction.name }) .defaults({ 'faas.coldstart': true, }) @@ -54,7 +56,7 @@ export async function generateData({ .rate(warmStartRate) .generator((timestamp) => instance - .transaction(warmStartTransaction.name) + .transaction({ transactionName: warmStartTransaction.name }) .defaults({ 'faas.coldstart': false, }) diff --git a/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts b/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts index a77a2e443b9d5..11bb01d3ca7bb 100644 --- a/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts +++ b/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts @@ -136,14 +136,20 @@ function generateApmData(synthtrace: ApmSynthtraceEsClient) { new Date('2021-10-01T00:01:00.000Z').getTime() ); - const instance = apm.service('multiple-env-service', 'production', 'go').instance('my-instance'); + const instance = apm + .service({ name: 'multiple-env-service', environment: 'production', agentName: 'go' }) + .instance('my-instance'); return synthtrace.index([ range .interval('1s') .rate(1) .generator((timestamp) => - instance.transaction('GET /api').timestamp(timestamp).duration(30).success() + instance + .transaction({ transactionName: 'GET /api' }) + .timestamp(timestamp) + .duration(30) + .success() ), ]); } diff --git a/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts b/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts index 1efab79996388..c7a5c43b05c05 100644 --- a/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts @@ -30,7 +30,9 @@ export async function generateData({ start: number; end: number; }) { - const instance = apm.service('synth-go', 'production', 'go').instance('instance-a'); + const instance = apm + .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) + .instance('instance-a'); const { rate, transaction, span } = dataConfig; await synthtraceEsClient.index( @@ -39,13 +41,13 @@ export async function generateData({ .rate(rate) .generator((timestamp) => instance - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .timestamp(timestamp) .duration(transaction.duration) .success() .children( instance - .span(span.name, span.type, span.subType) + .span({ spanName: span.name, spanType: span.type, spanSubtype: span.subType }) .duration(transaction.duration) .success() .destination(span.destination) diff --git a/x-pack/test/apm_api_integration/tests/dependencies/generate_operation_data.ts b/x-pack/test/apm_api_integration/tests/dependencies/generate_operation_data.ts index 7724b5fe334b6..7537e310bff22 100644 --- a/x-pack/test/apm_api_integration/tests/dependencies/generate_operation_data.ts +++ b/x-pack/test/apm_api_integration/tests/dependencies/generate_operation_data.ts @@ -27,8 +27,12 @@ export async function generateOperationData({ end: number; synthtraceEsClient: ApmSynthtraceEsClient; }) { - const synthGoInstance = apm.service('synth-go', 'production', 'go').instance('instance-a'); - const synthJavaInstance = apm.service('synth-java', 'development', 'java').instance('instance-a'); + const synthGoInstance = apm + .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) + .instance('instance-a'); + const synthJavaInstance = apm + .service({ name: 'synth-java', environment: 'development', agentName: 'java' }) + .instance('instance-a'); const interval = timerange(start, end).interval('1m'); @@ -37,7 +41,7 @@ export async function generateOperationData({ .rate(generateOperationDataConfig.ES_SEARCH_UNKNOWN_RATE) .generator((timestamp) => synthGoInstance - .span('/_search', 'db', 'elasticsearch') + .span({ spanName: '/_search', spanType: 'db', spanSubtype: 'elasticsearch' }) .destination('elasticsearch') .timestamp(timestamp) .duration(generateOperationDataConfig.ES_SEARCH_DURATION) @@ -46,7 +50,7 @@ export async function generateOperationData({ .rate(generateOperationDataConfig.ES_SEARCH_SUCCESS_RATE) .generator((timestamp) => synthGoInstance - .span('/_search', 'db', 'elasticsearch') + .span({ spanName: '/_search', spanType: 'db', spanSubtype: 'elasticsearch' }) .destination('elasticsearch') .timestamp(timestamp) .success() @@ -56,7 +60,7 @@ export async function generateOperationData({ .rate(generateOperationDataConfig.ES_SEARCH_FAILURE_RATE) .generator((timestamp) => synthGoInstance - .span('/_search', 'db', 'elasticsearch') + .span({ spanName: '/_search', spanType: 'db', spanSubtype: 'elasticsearch' }) .destination('elasticsearch') .timestamp(timestamp) .failure() @@ -66,7 +70,7 @@ export async function generateOperationData({ .rate(generateOperationDataConfig.ES_BULK_RATE) .generator((timestamp) => synthJavaInstance - .span('/_bulk', 'db', 'elasticsearch') + .span({ spanName: '/_bulk', spanType: 'db', spanSubtype: 'elasticsearch' }) .destination('elasticsearch') .timestamp(timestamp) .duration(generateOperationDataConfig.ES_BULK_DURATION) @@ -75,7 +79,7 @@ export async function generateOperationData({ .rate(generateOperationDataConfig.REDIS_SET_RATE) .generator((timestamp) => synthJavaInstance - .span('SET', 'db', 'redis') + .span({ spanName: 'SET', spanType: 'db', spanSubtype: 'redis' }) .destination('redis') .timestamp(timestamp) .duration(generateOperationDataConfig.REDIS_SET_DURATION) diff --git a/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts index 06890c0b6fd59..e93af3051d451 100644 --- a/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts +++ b/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts @@ -70,9 +70,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'Top dependency spans when data is loaded', { config: 'basic', archives: [] }, () => { - const javaInstance = apm.service('java', 'production', 'java').instance('instance-a'); + const javaInstance = apm + .service({ name: 'java', environment: 'production', agentName: 'java' }) + .instance('instance-a'); - const goInstance = apm.service('go', 'development', 'go').instance('instance-a'); + const goInstance = apm + .service({ name: 'go', environment: 'development', agentName: 'go' }) + .instance('instance-a'); before(async () => { await synthtraceEsClient.index([ @@ -81,40 +85,48 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(1) .generator((timestamp) => [ javaInstance - .span('without transaction', 'db', 'elasticsearch') + .span({ + spanName: 'without transaction', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .destination('elasticsearch') .duration(200) .timestamp(timestamp), javaInstance - .transaction('GET /api/my-endpoint') + .transaction({ transactionName: 'GET /api/my-endpoint' }) .duration(100) .timestamp(timestamp) .children( javaInstance - .span('/_search', 'db', 'elasticsearch') + .span({ spanName: '/_search', spanType: 'db', spanSubtype: 'elasticsearch' }) .destination('elasticsearch') .duration(100) .success() .timestamp(timestamp) ), goInstance - .transaction('GET /api/my-other-endpoint') + .transaction({ transactionName: 'GET /api/my-other-endpoint' }) .duration(100) .timestamp(timestamp) .children( goInstance - .span('/_search', 'db', 'elasticsearch') + .span({ spanName: '/_search', spanType: 'db', spanSubtype: 'elasticsearch' }) .destination('elasticsearch') .duration(50) .timestamp(timestamp) ), goInstance - .transaction('GET /api/my-other-endpoint') + .transaction({ transactionName: 'GET /api/my-other-endpoint' }) .duration(100) .timestamp(timestamp) .children( goInstance - .span('/_search', 'db', 'fake-elasticsearch') + .span({ + spanName: '/_search', + spanType: 'db', + spanSubtype: 'fake-elasticsearch', + }) .destination('fake-elasticsearch') .duration(50) .timestamp(timestamp) diff --git a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts index fe2f9c8cd2cf8..8b735b51d1e68 100644 --- a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts +++ b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts @@ -122,7 +122,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_PROD_ID_ERROR_RATE = 50; before(async () => { const serviceGoProdInstance = apm - .service(serviceName, 'production', 'go') + .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); const transactionNameProductList = 'GET /api/product/list'; @@ -134,7 +134,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_LIST_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductList) + .transaction({ transactionName: transactionNameProductList }) .timestamp(timestamp) .duration(1000) .success() @@ -144,7 +144,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_LIST_ERROR_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductList) + .transaction({ transactionName: transactionNameProductList }) .duration(1000) .timestamp(timestamp) .failure() @@ -154,7 +154,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_ID_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductId) + .transaction({ transactionName: transactionNameProductId }) .timestamp(timestamp) .duration(1000) .success() @@ -164,7 +164,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_ID_ERROR_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductId) + .transaction({ transactionName: transactionNameProductId }) .duration(1000) .timestamp(timestamp) .failure() diff --git a/x-pack/test/apm_api_integration/tests/error_rate/service_maps.spec.ts b/x-pack/test/apm_api_integration/tests/error_rate/service_maps.spec.ts index 8c14a203f7800..3610b240572aa 100644 --- a/x-pack/test/apm_api_integration/tests/error_rate/service_maps.spec.ts +++ b/x-pack/test/apm_api_integration/tests/error_rate/service_maps.spec.ts @@ -68,7 +68,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_PROD_ID_ERROR_RATE = 50; before(async () => { const serviceGoProdInstance = apm - .service(serviceName, 'production', 'go') + .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); const transactionNameProductList = 'GET /api/product/list'; @@ -80,7 +80,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_LIST_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductList, 'Worker') + .transaction({ + transactionName: transactionNameProductList, + transactionType: 'Worker', + }) .timestamp(timestamp) .duration(1000) .success() @@ -90,7 +93,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_LIST_ERROR_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductList, 'Worker') + .transaction({ + transactionName: transactionNameProductList, + transactionType: 'Worker', + }) .duration(1000) .timestamp(timestamp) .failure() @@ -100,7 +106,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_ID_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductId) + .transaction({ transactionName: transactionNameProductId }) .timestamp(timestamp) .duration(1000) .success() @@ -110,7 +116,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_ID_ERROR_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductId) + .transaction({ transactionName: transactionNameProductId }) .duration(1000) .timestamp(timestamp) .failure() diff --git a/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts b/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts index 5156ed7f05478..534850d2cc927 100644 --- a/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts +++ b/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts @@ -68,7 +68,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { }; before(async () => { - const serviceInstance = apm.service(serviceName, 'production', 'go').instance('instance-a'); + const serviceInstance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .instance('instance-a'); await synthtraceEsClient.index([ timerange(start, end) @@ -76,7 +78,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(appleTransaction.successRate) .generator((timestamp) => serviceInstance - .transaction(appleTransaction.name) + .transaction({ transactionName: appleTransaction.name }) .timestamp(timestamp) .duration(1000) .success() @@ -86,8 +88,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(appleTransaction.failureRate) .generator((timestamp) => serviceInstance - .transaction(appleTransaction.name) - .errors(serviceInstance.error('error 1', 'foo').timestamp(timestamp)) + .transaction({ transactionName: appleTransaction.name }) + .errors( + serviceInstance.error({ message: 'error 1', type: 'foo' }).timestamp(timestamp) + ) .duration(1000) .timestamp(timestamp) .failure() @@ -97,7 +101,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(bananaTransaction.successRate) .generator((timestamp) => serviceInstance - .transaction(bananaTransaction.name) + .transaction({ transactionName: bananaTransaction.name }) .timestamp(timestamp) .duration(1000) .success() @@ -107,8 +111,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(bananaTransaction.failureRate) .generator((timestamp) => serviceInstance - .transaction(bananaTransaction.name) - .errors(serviceInstance.error('error 2', 'bar').timestamp(timestamp)) + .transaction({ transactionName: bananaTransaction.name }) + .errors( + serviceInstance.error({ message: 'error 2', type: 'bar' }).timestamp(timestamp) + ) .duration(1000) .timestamp(timestamp) .failure() diff --git a/x-pack/test/apm_api_integration/tests/errors/generate_data.ts b/x-pack/test/apm_api_integration/tests/errors/generate_data.ts index 75d80b8599031..dee5dce49076d 100644 --- a/x-pack/test/apm_api_integration/tests/errors/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/errors/generate_data.ts @@ -31,7 +31,9 @@ export async function generateData({ start: number; end: number; }) { - const serviceGoProdInstance = apm.service(serviceName, 'production', 'go').instance('instance-a'); + const serviceGoProdInstance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .instance('instance-a'); const interval = '1m'; @@ -43,7 +45,7 @@ export async function generateData({ .rate(transaction.successRate) .generator((timestamp) => serviceGoProdInstance - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .timestamp(timestamp) .duration(1000) .success() @@ -54,9 +56,11 @@ export async function generateData({ .rate(transaction.failureRate) .generator((timestamp) => serviceGoProdInstance - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .errors( - serviceGoProdInstance.error(`Error ${index}`, transaction.name).timestamp(timestamp) + serviceGoProdInstance + .error({ message: `Error ${index}`, type: transaction.name }) + .timestamp(timestamp) ) .duration(1000) .timestamp(timestamp) diff --git a/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/generate_data.ts b/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/generate_data.ts index 6991c66e8eede..60f0e09875dbc 100644 --- a/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/generate_data.ts @@ -31,7 +31,9 @@ export async function generateData({ start: number; end: number; }) { - const serviceGoProdInstance = apm.service(serviceName, 'production', 'go').instance('instance-a'); + const serviceGoProdInstance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .instance('instance-a'); const interval = '1m'; @@ -43,7 +45,7 @@ export async function generateData({ .rate(transaction.successRate) .generator((timestamp) => serviceGoProdInstance - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .timestamp(timestamp) .duration(1000) .success() @@ -54,10 +56,10 @@ export async function generateData({ .rate(transaction.failureRate) .generator((timestamp) => serviceGoProdInstance - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .errors( serviceGoProdInstance - .error('Error 1', transaction.name, 'Error test') + .error({ message: 'Error 1', type: transaction.name, groupingName: 'Error test' }) .timestamp(timestamp) ) .duration(1000) diff --git a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/generate_data.ts b/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/generate_data.ts index 3e37fc871405f..e377b3c0db995 100644 --- a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/generate_data.ts @@ -31,7 +31,9 @@ export async function generateData({ start: number; end: number; }) { - const serviceGoProdInstance = apm.service(serviceName, 'production', 'go').instance('instance-a'); + const serviceGoProdInstance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .instance('instance-a'); const interval = '1m'; @@ -43,7 +45,7 @@ export async function generateData({ .rate(transaction.successRate) .generator((timestamp) => serviceGoProdInstance - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .timestamp(timestamp) .duration(1000) .success() @@ -54,13 +56,13 @@ export async function generateData({ .rate(transaction.failureRate) .generator((timestamp) => serviceGoProdInstance - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .errors( serviceGoProdInstance - .error(`Error 1 transaction ${transaction.name}`) + .error({ message: `Error 1 transaction ${transaction.name}` }) .timestamp(timestamp), serviceGoProdInstance - .error(`Error 2 transaction ${transaction.name}`) + .error({ message: `Error 2 transaction ${transaction.name}` }) .timestamp(timestamp) ) .duration(1000) diff --git a/x-pack/test/apm_api_integration/tests/infrastructure/generate_data.ts b/x-pack/test/apm_api_integration/tests/infrastructure/generate_data.ts index 5f4f80061ed2f..5bab490b303a2 100644 --- a/x-pack/test/apm_api_integration/tests/infrastructure/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/infrastructure/generate_data.ts @@ -17,10 +17,12 @@ export async function generateData({ end: number; }) { const serviceRunsInContainerInstance = apm - .service('synth-go', 'production', 'go') + .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) .instance('instance-a'); - const serviceInstance = apm.service('synth-java', 'production', 'java').instance('instance-b'); + const serviceInstance = apm + .service({ name: 'synth-java', environment: 'production', agentName: 'java' }) + .instance('instance-b'); await synthtraceEsClient.index( timerange(start, end) @@ -28,7 +30,7 @@ export async function generateData({ .generator((timestamp) => { return [ serviceRunsInContainerInstance - .transaction('GET /apple 🍎') + .transaction({ transactionName: 'GET /apple 🍎' }) .defaults({ 'container.id': 'foo', 'host.hostname': 'bar', @@ -38,7 +40,7 @@ export async function generateData({ .duration(1000) .success(), serviceInstance - .transaction('GET /banana 🍌') + .transaction({ transactionName: 'GET /banana 🍌' }) .defaults({ 'host.hostname': 'bar', }) diff --git a/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts b/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts index b38a7ef3052c2..48ffb06ed3bf2 100644 --- a/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts +++ b/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts @@ -124,10 +124,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_DEV_DURATION = 500; before(async () => { const serviceGoProdInstance = apm - .service(serviceName, 'production', 'go') + .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); const serviceGoDevInstance = apm - .service(serviceName, 'development', 'go') + .service({ name: serviceName, environment: 'development', agentName: 'go' }) .instance('instance-b'); await synthtraceEsClient.index([ @@ -136,7 +136,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .duration(GO_PROD_DURATION) .timestamp(timestamp) ), @@ -145,7 +145,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_DEV_RATE) .generator((timestamp) => serviceGoDevInstance - .transaction('GET /api/product/:id') + .transaction({ transactionName: 'GET /api/product/:id' }) .duration(GO_DEV_DURATION) .timestamp(timestamp) ), diff --git a/x-pack/test/apm_api_integration/tests/latency/service_maps.spec.ts b/x-pack/test/apm_api_integration/tests/latency/service_maps.spec.ts index e9b9749b659e2..5d1d3e6e62b54 100644 --- a/x-pack/test/apm_api_integration/tests/latency/service_maps.spec.ts +++ b/x-pack/test/apm_api_integration/tests/latency/service_maps.spec.ts @@ -67,10 +67,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_DEV_DURATION = 500; before(async () => { const serviceGoProdInstance = apm - .service(serviceName, 'production', 'go') + .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); const serviceGoDevInstance = apm - .service(serviceName, 'development', 'go') + .service({ name: serviceName, environment: 'development', agentName: 'go' }) .instance('instance-b'); await synthtraceEsClient.index([ @@ -79,7 +79,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction('GET /api/product/list', 'Worker') + .transaction({ + transactionName: 'GET /api/product/list', + transactionType: 'Worker', + }) .duration(GO_PROD_DURATION) .timestamp(timestamp) ), @@ -88,7 +91,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_DEV_RATE) .generator((timestamp) => serviceGoDevInstance - .transaction('GET /api/product/:id') + .transaction({ transactionName: 'GET /api/product/:id' }) .duration(GO_DEV_DURATION) .timestamp(timestamp) ), diff --git a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts index d25b17cc0dc6b..737efa4634de2 100644 --- a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts @@ -90,14 +90,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { const JAVA_PROD_RATE = 45; before(async () => { const serviceGoProdInstance = apm - .service('synth-go', 'production', 'go') + .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) .instance('instance-a'); const serviceGoDevInstance = apm - .service('synth-go', 'development', 'go') + .service({ name: 'synth-go', environment: 'development', agentName: 'go' }) .instance('instance-b'); const serviceJavaInstance = apm - .service('synth-java', 'production', 'java') + .service({ name: 'synth-java', environment: 'production', agentName: 'java' }) .instance('instance-c'); await synthtraceEsClient.index([ @@ -106,7 +106,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .duration(1000) .timestamp(timestamp) ), @@ -115,7 +115,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_DEV_RATE) .generator((timestamp) => serviceGoDevInstance - .transaction('GET /api/product/:id') + .transaction({ transactionName: 'GET /api/product/:id' }) .duration(1000) .timestamp(timestamp) ), @@ -124,7 +124,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(JAVA_PROD_RATE) .generator((timestamp) => serviceJavaInstance - .transaction('POST /api/product/buy') + .transaction({ transactionName: 'POST /api/product/buy' }) .duration(1000) .timestamp(timestamp) ), diff --git a/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts b/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts index c582c929c67cb..5aeb0e7db72a4 100644 --- a/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts +++ b/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts @@ -50,7 +50,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Service nodes when data is loaded', { config: 'basic', archives: [] }, () => { before(async () => { - const instance = apm.service(serviceName, 'production', 'go').instance(instanceName); + const instance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .instance(instanceName); await synthtraceEsClient.index( timerange(start, end) .interval('1m') diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts index 8313ec635c69a..80865880cd6c4 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts @@ -296,8 +296,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { const rangeEnd = new Date('2021-01-01T12:15:00.000Z').getTime() - 1; before(async () => { - const goService = apm.service('opbeans-go', 'production', 'go'); - const javaService = apm.service('opbeans-java', 'production', 'java'); + const goService = apm.service({ + name: 'opbeans-go', + environment: 'production', + agentName: 'go', + }); + const javaService = apm.service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }); const goInstanceA = goService.instance('go-instance-a'); const goInstanceB = goService.instance('go-instance-b'); @@ -310,7 +318,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { function withSpans(timestamp: number) { return new Array(3).fill(undefined).map(() => goInstanceA - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .timestamp(timestamp + 100) .duration(300) .destination('elasticsearch') @@ -321,7 +333,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { return synthtraceEsClient.index([ interval.rate(GO_A_INSTANCE_RATE_SUCCESS).generator((timestamp) => goInstanceA - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .success() .duration(500) .timestamp(timestamp) @@ -329,7 +341,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ), interval.rate(GO_A_INSTANCE_RATE_FAILURE).generator((timestamp) => goInstanceA - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .failure() .duration(500) .timestamp(timestamp) @@ -337,7 +349,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ), interval.rate(GO_B_INSTANCE_RATE_SUCCESS).generator((timestamp) => goInstanceB - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .success() .duration(500) .timestamp(timestamp) @@ -345,7 +357,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ), interval.rate(JAVA_INSTANCE_RATE).generator((timestamp) => javaInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .success() .duration(500) .timestamp(timestamp) diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts index 2999b2f68c29e..a93ac5420a785 100644 --- a/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts @@ -27,7 +27,9 @@ export async function generateData({ start: number; end: number; }) { - const serviceGoProdInstance = apm.service(serviceName, 'production', 'go').instance('instance-a'); + const serviceGoProdInstance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .instance('instance-a'); const transactionNameProductList = 'GET /api/product/list'; const transactionNameProductId = 'GET /api/product/:id'; @@ -47,7 +49,7 @@ export async function generateData({ .rate(PROD_LIST_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductList) + .transaction({ transactionName: transactionNameProductList }) .timestamp(timestamp) .duration(1000) .success() @@ -57,8 +59,10 @@ export async function generateData({ .rate(PROD_LIST_ERROR_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductList) - .errors(serviceGoProdInstance.error(ERROR_NAME_1, 'foo').timestamp(timestamp)) + .transaction({ transactionName: transactionNameProductList }) + .errors( + serviceGoProdInstance.error({ message: ERROR_NAME_1, type: 'foo' }).timestamp(timestamp) + ) .duration(1000) .timestamp(timestamp) .failure() @@ -68,7 +72,7 @@ export async function generateData({ .rate(PROD_ID_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductId) + .transaction({ transactionName: transactionNameProductId }) .timestamp(timestamp) .duration(1000) .success() @@ -78,8 +82,10 @@ export async function generateData({ .rate(PROD_ID_ERROR_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionNameProductId) - .errors(serviceGoProdInstance.error(ERROR_NAME_2, 'bar').timestamp(timestamp)) + .transaction({ transactionName: transactionNameProductId }) + .errors( + serviceGoProdInstance.error({ message: ERROR_NAME_2, type: 'bar' }).timestamp(timestamp) + ) .duration(1000) .timestamp(timestamp) .failure() diff --git a/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts b/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts index 49160f9b5caf2..faa160063dfa1 100644 --- a/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts @@ -57,7 +57,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: [] }, () => { before(async () => { - const instance = apm.service(serviceName, 'production', 'go').instance(instanceName); + const instance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .instance(instanceName); await synthtraceEsClient.index( timerange(start, end) .interval('1m') @@ -65,7 +67,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .generator((timestamp) => instance .containerId(instanceName) - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .timestamp(timestamp) .duration(1000) .success() diff --git a/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts b/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts index 4765f52b855d3..22e8e12c66817 100644 --- a/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts @@ -65,7 +65,9 @@ export async function generateData({ const { name: serviceRunTimeName, version: serviceRunTimeVersion } = runtime; const { name: agentName, version: agentVersion } = agent; - const instance = apm.service(serviceName, 'production', agentName).instance('instance-a'); + const instance = apm + .service({ name: serviceName, environment: 'production', agentName }) + .instance('instance-a'); const traceEvents = [ timerange(start, end) @@ -74,7 +76,7 @@ export async function generateData({ .generator((timestamp) => instance .containerId('instance-a') - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .timestamp(timestamp) .defaults({ 'cloud.provider': provider, @@ -101,7 +103,7 @@ export async function generateData({ .rate(rate) .generator((timestamp) => instance - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .timestamp(timestamp) .defaults({ 'cloud.provider': provider, diff --git a/x-pack/test/apm_api_integration/tests/services/service_icons/generate_data.ts b/x-pack/test/apm_api_integration/tests/services/service_icons/generate_data.ts index 05f63f5ac05af..36b0e30f92efd 100644 --- a/x-pack/test/apm_api_integration/tests/services/service_icons/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_icons/generate_data.ts @@ -33,14 +33,16 @@ export async function generateData({ const { serviceName, agentName, rate, cloud, transaction } = dataConfig; const { provider, serviceName: cloudServiceName } = cloud; - const instance = apm.service(serviceName, 'production', agentName).instance('instance-a'); + const instance = apm + .service({ name: serviceName, environment: 'production', agentName }) + .instance('instance-a'); const traceEvents = timerange(start, end) .interval('30s') .rate(rate) .generator((timestamp) => instance - .transaction(transaction.name) + .transaction({ transactionName: transaction.name }) .defaults({ 'kubernetes.pod.uid': 'test', 'cloud.provider': provider, diff --git a/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts b/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts index ca2cb0c3d20a7..762722a77be79 100644 --- a/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/sorted_and_filtered_services.spec.ts @@ -53,11 +53,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { // FLAKY: https://github.com/elastic/kibana/issues/127939 registry.when.skip('Sorted and filtered services', { config: 'trial', archives: [] }, () => { before(async () => { - const serviceA = apm.service(SERVICE_NAME_PREFIX + 'a', 'production', 'java').instance('a'); + const serviceA = apm + .service({ name: SERVICE_NAME_PREFIX + 'a', environment: 'production', agentName: 'java' }) + .instance('a'); - const serviceB = apm.service(SERVICE_NAME_PREFIX + 'b', 'development', 'go').instance('b'); + const serviceB = apm + .service({ name: SERVICE_NAME_PREFIX + 'b', environment: 'development', agentName: 'go' }) + .instance('b'); - const serviceC = apm.service(SERVICE_NAME_PREFIX + 'c', 'development', 'go').instance('c'); + const serviceC = apm + .service({ name: SERVICE_NAME_PREFIX + 'c', environment: 'development', agentName: 'go' }) + .instance('c'); const spikeStart = new Date('2021-01-07T12:00:00.000Z').getTime(); const spikeEnd = new Date('2021-01-07T14:00:00.000Z').getTime(); @@ -69,11 +75,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { const isInSpike = spikeStart <= timestamp && spikeEnd >= timestamp; return [ serviceA - .transaction('GET /api') + .transaction({ transactionName: 'GET /api' }) .duration(isInSpike ? 1000 : 1100) .timestamp(timestamp), serviceB - .transaction('GET /api') + .transaction({ transactionName: 'GET /api' }) .duration(isInSpike ? 1000 : 4000) .timestamp(timestamp), ]; @@ -86,7 +92,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { .interval('15m') .rate(1) .generator((timestamp) => { - return serviceC.transaction('GET /api', 'custom').duration(1000).timestamp(timestamp); + return serviceC + .transaction({ transactionName: 'GET /api', transactionType: 'custom' }) + .duration(1000) + .timestamp(timestamp); }); await synthtraceClient.index(eventsWithinTimerange.merge(eventsOutsideOfTimerange)); diff --git a/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts b/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts index c9c95b2e99bbc..ffd6d43bec959 100644 --- a/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts @@ -71,14 +71,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { const serviceGoProdInstance = apm - .service(serviceName, 'production', 'go') + .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); const serviceGoDevInstance = apm - .service(serviceName, 'development', 'go') + .service({ name: serviceName, environment: 'development', agentName: 'go' }) .instance('instance-b'); const serviceJavaInstance = apm - .service('synth-java', 'development', 'java') + .service({ name: 'synth-java', environment: 'development', agentName: 'java' }) .instance('instance-c'); await synthtraceEsClient.index([ @@ -87,7 +87,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .duration(1000) .timestamp(timestamp) ), @@ -96,7 +96,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_DEV_RATE) .generator((timestamp) => serviceGoDevInstance - .transaction('GET /api/product/:id') + .transaction({ transactionName: 'GET /api/product/:id' }) .duration(1000) .timestamp(timestamp) ), @@ -105,7 +105,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(JAVA_PROD_RATE) .generator((timestamp) => serviceJavaInstance - .transaction('POST /api/product/buy') + .transaction({ transactionName: 'POST /api/product/buy' }) .duration(1000) .timestamp(timestamp) ), diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts b/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts index 898f12ceaeffb..c8b21729484bc 100644 --- a/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts @@ -71,19 +71,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { const errorInterval = range.interval('5s'); const multipleEnvServiceProdInstance = apm - .service('multiple-env-service', 'production', 'go') + .service({ name: 'multiple-env-service', environment: 'production', agentName: 'go' }) .instance('multiple-env-service-production'); const multipleEnvServiceDevInstance = apm - .service('multiple-env-service', 'development', 'go') + .service({ name: 'multiple-env-service', environment: 'development', agentName: 'go' }) .instance('multiple-env-service-development'); const metricOnlyInstance = apm - .service('metric-only-service', 'production', 'java') + .service({ name: 'metric-only-service', environment: 'production', agentName: 'java' }) .instance('metric-only-production'); const errorOnlyInstance = apm - .service('error-only-service', 'production', 'java') + .service({ name: 'error-only-service', environment: 'production', agentName: 'java' }) .instance('error-only-production'); const config = { @@ -105,7 +105,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(config.multiple.prod.rps) .generator((timestamp) => multipleEnvServiceProdInstance - .transaction('GET /api') + .transaction({ transactionName: 'GET /api' }) .timestamp(timestamp) .duration(config.multiple.prod.duration) .success() @@ -114,7 +114,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(config.multiple.dev.rps) .generator((timestamp) => multipleEnvServiceDevInstance - .transaction('GET /api') + .transaction({ transactionName: 'GET /api' }) .timestamp(timestamp) .duration(config.multiple.dev.duration) .failure() @@ -123,7 +123,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(config.multiple.prod.rps) .generator((timestamp) => multipleEnvServiceDevInstance - .transaction('non-request', 'rpc') + .transaction({ transactionName: 'non-request', transactionType: 'rpc' }) .timestamp(timestamp) .duration(config.multiple.prod.duration) .success() @@ -140,7 +140,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { ), errorInterval .rate(1) - .generator((timestamp) => errorOnlyInstance.error('Foo').timestamp(timestamp)), + .generator((timestamp) => + errorOnlyInstance.error({ message: 'Foo' }).timestamp(timestamp) + ), ]); }); diff --git a/x-pack/test/apm_api_integration/tests/span_links/data_generator.ts b/x-pack/test/apm_api_integration/tests/span_links/data_generator.ts index 37bd72ff71c59..0af23ab0dc736 100644 --- a/x-pack/test/apm_api_integration/tests/span_links/data_generator.ts +++ b/x-pack/test/apm_api_integration/tests/span_links/data_generator.ts @@ -10,7 +10,7 @@ import uuid from 'uuid'; function getProducerInternalOnly() { const producerInternalOnlyInstance = apm - .service('producer-internal-only', 'production', 'go') + .service({ name: 'producer-internal-only', environment: 'production', agentName: 'go' }) .instance('instance a'); const events = timerange( @@ -21,13 +21,13 @@ function getProducerInternalOnly() { .rate(1) .generator((timestamp) => { return producerInternalOnlyInstance - .transaction(`Transaction A`) + .transaction({ transactionName: `Transaction A` }) .timestamp(timestamp) .duration(1000) .success() .children( producerInternalOnlyInstance - .span(`Span A`, 'external', 'http') + .span({ spanName: `Span A`, spanType: 'external', spanSubtype: 'http' }) .timestamp(timestamp + 50) .duration(100) .success() @@ -57,7 +57,7 @@ function getProducerInternalOnly() { function getProducerExternalOnly() { const producerExternalOnlyInstance = apm - .service('producer-external-only', 'production', 'java') + .service({ name: 'producer-external-only', environment: 'production', agentName: 'java' }) .instance('instance b'); const events = timerange( @@ -68,13 +68,13 @@ function getProducerExternalOnly() { .rate(1) .generator((timestamp) => { return producerExternalOnlyInstance - .transaction(`Transaction B`) + .transaction({ transactionName: `Transaction B` }) .timestamp(timestamp) .duration(1000) .success() .children( producerExternalOnlyInstance - .span(`Span B`, 'external', 'http') + .span({ spanName: `Span B`, spanType: 'external', spanSubtype: 'http' }) .defaults({ 'span.links': [{ trace: { id: 'trace#1' }, span: { id: 'span#1' } }], }) @@ -82,7 +82,7 @@ function getProducerExternalOnly() { .duration(100) .success(), producerExternalOnlyInstance - .span(`Span B.1`, 'external', 'http') + .span({ spanName: `Span B.1`, spanType: 'external', spanSubtype: 'http' }) .timestamp(timestamp + 50) .duration(100) .success() @@ -130,7 +130,7 @@ function getProducerConsumer({ const externalTraceId = uuid.v4(); const producerConsumerInstance = apm - .service('producer-consumer', 'production', 'ruby') + .service({ name: 'producer-consumer', environment: 'production', agentName: 'ruby' }) .instance('instance c'); const events = timerange( @@ -141,7 +141,7 @@ function getProducerConsumer({ .rate(1) .generator((timestamp) => { return producerConsumerInstance - .transaction(`Transaction C`) + .transaction({ transactionName: `Transaction C` }) .defaults({ 'span.links': [ producerInternalOnlySpanASpanLink, @@ -154,7 +154,7 @@ function getProducerConsumer({ .success() .children( producerConsumerInstance - .span(`Span C`, 'external', 'http') + .span({ spanName: `Span C`, spanType: 'external', spanSubtype: 'http' }) .timestamp(timestamp + 50) .duration(100) .success() @@ -200,7 +200,7 @@ function getConsumerMultiple({ producerConsumerTransactionCLink: SpanLink; }) { const consumerMultipleInstance = apm - .service('consumer-multiple', 'production', 'nodejs') + .service({ name: 'consumer-multiple', environment: 'production', agentName: 'nodejs' }) .instance('instance d'); const events = timerange( @@ -211,14 +211,14 @@ function getConsumerMultiple({ .rate(1) .generator((timestamp) => { return consumerMultipleInstance - .transaction(`Transaction D`) + .transaction({ transactionName: `Transaction D` }) .defaults({ 'span.links': [producerInternalOnlySpanALink, producerConsumerSpanCLink] }) .timestamp(timestamp) .duration(1000) .success() .children( consumerMultipleInstance - .span(`Span E`, 'external', 'http') + .span({ spanName: `Span E`, spanType: 'external', spanSubtype: 'http' }) .defaults({ 'span.links': [producerExternalOnlySpanBLink, producerConsumerTransactionCLink], }) diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts index fe0034c23e0ba..632e17c37509a 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts @@ -56,21 +56,31 @@ export default function ApiTest({ getService }: FtrProviderContext) { let status: number; before(async () => { - const serviceGo1 = apm.service('synth-go-1', 'production', 'go').instance('instance'); - const serviceGo2 = apm.service('synth-go-2', 'production', 'go').instance('instance'); + const serviceGo1 = apm + .service({ name: 'synth-go-1', environment: 'production', agentName: 'go' }) + .instance('instance'); + const serviceGo2 = apm + .service({ name: 'synth-go-2', environment: 'production', agentName: 'go' }) + .instance('instance'); await synthtraceEsClient.index([ timerange(start, end) .interval('5m') .rate(1) .generator((timestamp) => - serviceGo1.transaction('GET /api/product/list1').duration(2000).timestamp(timestamp) + serviceGo1 + .transaction({ transactionName: 'GET /api/product/list1' }) + .duration(2000) + .timestamp(timestamp) ), timerange(start, end) .interval('5m') .rate(1) .generator((timestamp) => - serviceGo2.transaction('GET /api/product/list2').duration(2000).timestamp(timestamp) + serviceGo2 + .transaction({ transactionName: 'GET /api/product/list2' }) + .duration(2000) + .timestamp(timestamp) ), ]); diff --git a/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts b/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts index c990a7c632caa..926724156d553 100644 --- a/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts +++ b/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts @@ -99,10 +99,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const JAVA_PROD_RATE = 25; before(async () => { const serviceGoProdInstance = apm - .service('synth-go', 'production', 'go') + .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) .instance('instance-a'); const serviceJavaInstance = apm - .service('synth-java', 'development', 'java') + .service({ name: 'synth-java', environment: 'development', agentName: 'java' }) .instance('instance-c'); await synthtraceEsClient.index([ @@ -111,22 +111,30 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .duration(1000) .timestamp(timestamp) .children( serviceGoProdInstance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .duration(1000) .success() .destination('elasticsearch') .timestamp(timestamp), serviceGoProdInstance - .span('custom_operation', 'app') + .span({ spanName: 'custom_operation', spanType: 'app' }) .duration(550) .children( serviceGoProdInstance - .span('SELECT FROM products', 'db', 'postgresql') + .span({ + spanName: 'SELECT FROM products', + spanType: 'db', + spanSubtype: 'postgresql', + }) .duration(500) .success() .destination('postgresql') @@ -141,18 +149,22 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(JAVA_PROD_RATE) .generator((timestamp) => serviceJavaInstance - .transaction('POST /api/product/buy') + .transaction({ transactionName: 'POST /api/product/buy' }) .duration(1000) .timestamp(timestamp) .children( serviceJavaInstance - .span('GET apm-*/_search', 'db', 'elasticsearch') + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .duration(1000) .success() .destination('elasticsearch') .timestamp(timestamp), serviceJavaInstance - .span('custom_operation', 'app') + .span({ spanName: 'custom_operation', spanType: 'app' }) .duration(50) .success() .timestamp(timestamp) diff --git a/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts b/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts index ef091dc83a429..d4d843e8cc663 100644 --- a/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts +++ b/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts @@ -110,10 +110,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_DEV_RATE = 20; before(async () => { const serviceGoProdInstance = apm - .service(serviceName, 'production', 'go') + .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); const serviceGoDevInstance = apm - .service(serviceName, 'development', 'go') + .service({ name: serviceName, environment: 'development', agentName: 'go' }) .instance('instance-b'); await synthtraceEsClient.index([ @@ -122,7 +122,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction('GET /api/product/list') + .transaction({ transactionName: 'GET /api/product/list' }) .duration(1000) .timestamp(timestamp) ), @@ -131,7 +131,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_DEV_RATE) .generator((timestamp) => serviceGoDevInstance - .transaction('GET /api/product/:id') + .transaction({ transactionName: 'GET /api/product/:id' }) .duration(1000) .timestamp(timestamp) ), diff --git a/x-pack/test/apm_api_integration/tests/throughput/service_maps.spec.ts b/x-pack/test/apm_api_integration/tests/throughput/service_maps.spec.ts index fd775ec9af2a9..039a4f0f548b0 100644 --- a/x-pack/test/apm_api_integration/tests/throughput/service_maps.spec.ts +++ b/x-pack/test/apm_api_integration/tests/throughput/service_maps.spec.ts @@ -75,10 +75,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_DEV_RATE = 20; before(async () => { const serviceGoProdInstance = apm - .service(serviceName, 'production', 'go') + .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); const serviceGoDevInstance = apm - .service(serviceName, 'development', 'go') + .service({ name: serviceName, environment: 'development', agentName: 'go' }) .instance('instance-b'); await synthtraceEsClient.index([ @@ -87,7 +87,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction('GET /apple 🍎 ', 'Worker') + .transaction({ transactionName: 'GET /apple 🍎 ', transactionType: 'Worker' }) .duration(1000) .timestamp(timestamp) ), @@ -95,7 +95,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { .interval('1m') .rate(GO_DEV_RATE) .generator((timestamp) => - serviceGoDevInstance.transaction('GET /apple 🍎 ').duration(1000).timestamp(timestamp) + serviceGoDevInstance + .transaction({ transactionName: 'GET /apple 🍎 ' }) + .duration(1000) + .timestamp(timestamp) ), ]); }); diff --git a/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts b/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts index 0c0696f801a95..a2a44e7d086da 100644 --- a/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts +++ b/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts @@ -97,11 +97,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Find traces when traces exist', { config: 'basic', archives: [] }, () => { before(() => { - const java = apm.service('java', 'production', 'java').instance('java'); + const java = apm + .service({ name: 'java', environment: 'production', agentName: 'java' }) + .instance('java'); - const node = apm.service('node', 'development', 'nodejs').instance('node'); + const node = apm + .service({ name: 'node', environment: 'development', agentName: 'nodejs' }) + .instance('node'); - const python = apm.service('python', 'production', 'python').instance('python'); + const python = apm + .service({ name: 'python', environment: 'production', agentName: 'python' }) + .instance('python'); function generateTrace(timestamp: number, order: Instance[], db?: 'elasticsearch' | 'redis') { return order @@ -114,7 +120,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const time = timestamp + invertedIndex * 10; const transaction: Transaction = instance - .transaction(`GET /${instance.fields['service.name']!}/api`) + .transaction({ transactionName: `GET /${instance.fields['service.name']!}/api` }) .timestamp(time) .duration(duration); @@ -122,7 +128,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const next = order[invertedIndex + 1].fields['service.name']!; transaction.children( instance - .span(`GET ${next}/api`, 'external', 'http') + .span({ spanName: `GET ${next}/api`, spanType: 'external', spanSubtype: 'http' }) .destination(next) .duration(duration) .timestamp(time + 1) @@ -131,7 +137,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { } else if (db) { transaction.children( instance - .span(db, 'db', db) + .span({ spanName: db, spanType: 'db', spanSubtype: db }) .destination(db) .duration(duration) .timestamp(time + 1) diff --git a/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts b/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts index 509d70caf5291..087ec58e83806 100644 --- a/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts +++ b/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts @@ -55,25 +55,31 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Trace exists', { config: 'basic', archives: [] }, () => { let serviceATraceId: string; before(async () => { - const instanceJava = apm.service('synth-apple', 'production', 'java').instance('instance-b'); + const instanceJava = apm + .service({ name: 'synth-apple', environment: 'production', agentName: 'java' }) + .instance('instance-b'); const events = timerange(start, end) .interval('1m') .rate(1) .generator((timestamp) => { return [ instanceJava - .transaction('GET /apple 🍏') + .transaction({ transactionName: 'GET /apple 🍏' }) .timestamp(timestamp) .duration(1000) .failure() .errors( instanceJava - .error('[ResponseError] index_not_found_exception') + .error({ message: '[ResponseError] index_not_found_exception' }) .timestamp(timestamp + 50) ) .children( instanceJava - .span('get_green_apple_🍏', 'db', 'elasticsearch') + .span({ + spanName: 'get_green_apple_🍏', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) .timestamp(timestamp + 50) .duration(900) .success() diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts index 350830abcbba3..7215dd933c87e 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts @@ -81,7 +81,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_PROD_ERROR_RATE = 25; before(async () => { const serviceGoProdInstance = apm - .service(serviceName, 'production', 'go') + .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); const transactionName = 'GET /api/product/list'; @@ -92,7 +92,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionName) + .transaction({ transactionName }) .timestamp(timestamp) .duration(1000) .success() @@ -102,7 +102,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(GO_PROD_ERROR_RATE) .generator((timestamp) => serviceGoProdInstance - .transaction(transactionName) + .transaction({ transactionName }) .duration(1000) .timestamp(timestamp) .failure() From 1d1f060071a8f3cda8148a98af8027d4c27c7c42 Mon Sep 17 00:00:00 2001 From: Baturalp Gurdin <9674241+suchcodemuchwow@users.noreply.github.com> Date: Tue, 13 Sep 2022 16:47:25 +0300 Subject: [PATCH 14/28] skip flaky jest suite (#140620) --- .../components/artifact_list_page/artifact_list_page.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.test.tsx index 86ec7431bd801..9160732e32b3e 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.test.tsx @@ -16,7 +16,8 @@ import { getDeferred } from '../mocks'; jest.mock('../../../common/components/user_privileges'); -describe('When using the ArtifactListPage component', () => { +// FLAKY: https://github.com/elastic/kibana/issues/140620 +describe.skip('When using the ArtifactListPage component', () => { let render: ( props?: Partial ) => ReturnType; From a30356314f7c4d6fe0ae13c24c78050271abc91a Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Tue, 13 Sep 2022 09:59:18 -0400 Subject: [PATCH 15/28] fix loading cell between pagination (#140440) Co-authored-by: Maryam Saeidi --- .../sections/alerts_table/alerts_table.tsx | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx index f8092475f1ba0..302392674ccdd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx @@ -15,6 +15,7 @@ import { EuiToolTip, EuiButtonIcon, EuiDataGridStyle, + EuiLoadingContent, } from '@elastic/eui'; import { useSorting, usePagination, useBulkActions } from './hooks'; import { AlertsTableProps } from '../../../types'; @@ -219,16 +220,21 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab (_props: EuiDataGridCellValueElementProps) => { // https://github.com/elastic/eui/issues/5811 const alert = alerts[_props.rowIndex - pagination.pageSize * pagination.pageIndex]; - const data: Array<{ field: string; value: string[] }> = []; - Object.entries(alert ?? {}).forEach(([key, value]) => { - data.push({ field: key, value: value as string[] }); - }); - return renderCellValue({ - ..._props, - data, - }); + if (alert) { + const data: Array<{ field: string; value: string[] }> = []; + Object.entries(alert ?? {}).forEach(([key, value]) => { + data.push({ field: key, value: value as string[] }); + }); + return renderCellValue({ + ..._props, + data, + }); + } else if (isLoading) { + return ; + } + return null; }, - [alerts, pagination.pageIndex, pagination.pageSize, renderCellValue] + [alerts, isLoading, pagination.pageIndex, pagination.pageSize, renderCellValue] ); return ( From 926badd112169cf870a8251cfa02c789dafbb785 Mon Sep 17 00:00:00 2001 From: Ashokaditya <1849116+ashokaditya@users.noreply.github.com> Date: Tue, 13 Sep 2022 16:11:06 +0200 Subject: [PATCH 16/28] [Security Solution][Endpoint][Response Actions] Persist actions log filter values via url params on Action History page (#140474) * persist actions log filter values via url params fixes elastic/security-team/issues/4742 * move URL param hook to response actions components fixes elastic/security-team/issues/4742 * add more tests fixes elastic/security-team/issues/4742 * update tests * verify commands and statuses are not undefined review changes (@paul-tavares) --- .../actions_log_date_range_picker.tsx | 16 +- .../components/actions_log_filter.tsx | 68 +++- .../components/actions_log_filters.tsx | 12 +- .../components/hooks.tsx | 91 ++++- .../use_action_history_url_params.test.ts | 109 ++++++ .../use_action_history_url_params.ts | 194 +++++++++++ .../endpoint_response_actions_list/mocks.tsx | 75 +++++ .../response_actions_log.test.tsx | 95 ++---- .../response_actions_log.tsx | 94 ++++-- .../view/response_actions_list_page.test.tsx | 316 ++++++++++++++++++ .../view/response_actions_list_page.tsx | 2 +- 11 files changed, 954 insertions(+), 118 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.test.ts create mode 100644 x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts create mode 100644 x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/mocks.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx index 015fd3a501621..385449d8754b6 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx @@ -18,6 +18,7 @@ import type { import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; +import { useActionHistoryUrlParams } from './use_action_history_url_params'; export interface DateRangePickerValues { autoRefreshOptions: { @@ -37,16 +38,19 @@ export const ActionLogDateRangePicker = memo( ({ dateRangePickerState, isDataLoading, + isFlyout, onRefresh, onRefreshChange, onTimeChange, }: { dateRangePickerState: DateRangePickerValues; isDataLoading: boolean; + isFlyout: boolean; onRefresh: () => void; onRefreshChange: (evt: OnRefreshChangeProps) => void; onTimeChange: ({ start, end }: DurationRange) => void; }) => { + const { startDate: startDateFromUrl, endDate: endDateFromUrl } = useActionHistoryUrlParams(); const getTestId = useTestIdGenerator('response-actions-list'); const kibana = useKibana(); const { uiSettings } = kibana.services; @@ -72,14 +76,22 @@ export const ActionLogDateRangePicker = memo( isLoading={isDataLoading} dateFormat={uiSettings.get('dateFormat')} commonlyUsedRanges={commonlyUsedRanges} - end={dateRangePickerState.endDate} + end={ + isFlyout + ? dateRangePickerState.endDate + : endDateFromUrl ?? dateRangePickerState.endDate + } isPaused={!dateRangePickerState.autoRefreshOptions.enabled} onTimeChange={onTimeChange} onRefreshChange={onRefreshChange} refreshInterval={dateRangePickerState.autoRefreshOptions.duration} onRefresh={onRefresh} recentlyUsedRanges={dateRangePickerState.recentlyUsedDateRanges} - start={dateRangePickerState.startDate} + start={ + isFlyout + ? dateRangePickerState.startDate + : startDateFromUrl ?? dateRangePickerState.startDate + } showUpdateButton={false} updateButtonProps={{ iconOnly: true, fill: false }} width="auto" diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx index 9a5903a278fb7..e26f1375984b5 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx @@ -7,8 +7,9 @@ import React, { memo, useMemo, useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiPopoverTitle } from '@elastic/eui'; +import type { ResponseActions } from '../../../../../common/endpoint/service/response_actions/constants'; import { ActionsLogFilterPopover } from './actions_log_filter_popover'; -import { type FilterItems, type FilterName, useActionsLogFilter } from './hooks'; +import { type FilterItems, type FilterName, useActionsLogFilter, getUiCommand } from './hooks'; import { ClearAllButton } from './clear_all_button'; import { UX_MESSAGES } from '../translations'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; @@ -16,22 +17,32 @@ import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; export const ActionsLogFilter = memo( ({ filterName, + isFlyout, onChangeFilterOptions, }: { filterName: FilterName; + isFlyout: boolean; onChangeFilterOptions: (selectedOptions: string[]) => void; }) => { const getTestId = useTestIdGenerator('response-actions-list'); - const { items, setItems, hasActiveFilters, numActiveFilters, numFilters } = - useActionsLogFilter(filterName); + const { + items, + setItems, + hasActiveFilters, + numActiveFilters, + numFilters, + setUrlActionsFilters, + setUrlStatusesFilters, + } = useActionsLogFilter(filterName, isFlyout); const isSearchable = useMemo(() => filterName !== 'statuses', [filterName]); const onChange = useCallback( (newOptions: FilterItems) => { - setItems(newOptions.map((e) => e)); + // update filter UI options state + setItems(newOptions.map((option) => option)); - // update selected filter state + // compute selected list of options const selectedItems = newOptions.reduce((acc, curr) => { if (curr.checked === 'on') { acc.push(curr.key); @@ -39,22 +50,59 @@ export const ActionsLogFilter = memo( return acc; }, []); + if (!isFlyout) { + // update URL params + if (filterName === 'actions') { + setUrlActionsFilters( + selectedItems.map((item) => getUiCommand(item as ResponseActions)).join() + ); + } else if (filterName === 'statuses') { + setUrlStatusesFilters(selectedItems.join()); + } + } + // update query state onChangeFilterOptions(selectedItems); }, - [setItems, onChangeFilterOptions] + [ + filterName, + isFlyout, + setItems, + onChangeFilterOptions, + setUrlActionsFilters, + setUrlStatusesFilters, + ] ); // clear all selected options const onClearAll = useCallback(() => { + // update filter UI options state setItems( - items.map((e) => { - e.checked = undefined; - return e; + items.map((option) => { + option.checked = undefined; + return option; }) ); + + if (!isFlyout) { + // update URL params + if (filterName === 'actions') { + setUrlActionsFilters(''); + } else if (filterName === 'statuses') { + setUrlStatusesFilters(''); + } + } + // update query state onChangeFilterOptions([]); - }, [items, setItems, onChangeFilterOptions]); + }, [ + filterName, + isFlyout, + items, + setItems, + onChangeFilterOptions, + setUrlActionsFilters, + setUrlStatusesFilters, + ]); return ( void; onChangeStatusesFilter: (selectedStatuses: string[]) => void; onRefresh: () => void; @@ -43,14 +45,19 @@ export const ActionsLogFilters = memo( // TODO: add more filter names here (users, hosts, statuses) return ( <> - + ); - }, [onChangeCommandsFilter, onChangeStatusesFilter]); + }, [isFlyout, onChangeCommandsFilter, onChangeStatusesFilter]); const onClickRefreshButton = useCallback(() => onClick(), [onClick]); @@ -63,6 +70,7 @@ export const ActionsLogFilters = memo( { +export const useDateRangePicker = (isFlyout: boolean) => { + const { setUrlDateRangeFilters } = useActionHistoryUrlParams(); const [dateRangePickerState, setDateRangePickerState] = useState(defaultDateRangeOptions); @@ -86,9 +88,16 @@ export const useDateRangePicker = () => { .slice(0, 9), ]; updateActionListRecentlyUsedDateRanges(newRecentlyUsedDateRanges); + + // update URL params for date filters + if (!isFlyout) { + setUrlDateRangeFilters({ startDate: newStart, endDate: newEnd }); + } }, [ dateRangePickerState.recentlyUsedDateRanges, + isFlyout, + setUrlDateRangeFilters, updateActionListDateRanges, updateActionListRecentlyUsedDateRanges, ] @@ -101,6 +110,7 @@ export type FilterItems = Array<{ key: string; label: string; checked: 'on' | undefined; + 'data-test-subj': string; }>; export const getActionStatus = (status: ResponseActionStatus): string => { @@ -114,41 +124,84 @@ export const getActionStatus = (status: ResponseActionStatus): string => { return ''; }; -export const getCommand = ( +/** + * map actual command to ui command + * unisolate -> release + * running-processes -> processes + */ +export const getUiCommand = ( command: ResponseActions -): Exclude | 'release' | 'processes' => - command === 'unisolate' ? 'release' : command === 'running-processes' ? 'processes' : command; +): Exclude | 'release' | 'processes' => { + if (command === 'unisolate') { + return 'release'; + } else if (command === 'running-processes') { + return 'processes'; + } else { + return command; + } +}; + +/** + * map UI command back to actual command + * release -> unisolate + * processes -> running-processes + */ +export const getCommandKey = ( + uiCommand: Exclude | 'release' | 'processes' +): ResponseActions => { + if (uiCommand === 'release') { + return 'unisolate'; + } else if (uiCommand === 'processes') { + return 'running-processes'; + } else { + return uiCommand; + } +}; // TODO: add more filter names here export type FilterName = keyof typeof FILTER_NAMES; export const useActionsLogFilter = ( - filterName: FilterName + filterName: FilterName, + isFlyout: boolean ): { items: FilterItems; setItems: React.Dispatch>; hasActiveFilters: boolean; numActiveFilters: number; numFilters: number; + setUrlActionsFilters: ReturnType['setUrlActionsFilters']; + setUrlStatusesFilters: ReturnType['setUrlStatusesFilters']; } => { + const { commands, statuses, setUrlActionsFilters, setUrlStatusesFilters } = + useActionHistoryUrlParams(); const isStatusesFilter = filterName === 'statuses'; const [items, setItems] = useState( isStatusesFilter - ? RESPONSE_ACTION_STATUS.map((filter) => ({ - key: filter, + ? RESPONSE_ACTION_STATUS.map((statusName) => ({ + key: statusName, label: ( ) as unknown as string, - checked: undefined, + checked: !isFlyout && statuses?.includes(statusName) ? 'on' : undefined, + 'data-test-subj': `${filterName}-filter-option`, })) - : RESPONSE_ACTION_COMMANDS.map((filter) => ({ - key: filter, - label: getCommand(filter), - checked: undefined, + : RESPONSE_ACTION_COMMANDS.map((commandName) => ({ + key: commandName, + label: getUiCommand(commandName), + checked: + !isFlyout && commands?.map((command) => getCommandKey(command)).includes(commandName) + ? 'on' + : undefined, + 'data-test-subj': `${filterName}-filter-option`, })) ); @@ -159,5 +212,13 @@ export const useActionsLogFilter = ( ); const numFilters = useMemo(() => items.filter((item) => item.checked !== 'on').length, [items]); - return { items, setItems, hasActiveFilters, numActiveFilters, numFilters }; + return { + items, + setItems, + hasActiveFilters, + numActiveFilters, + numFilters, + setUrlActionsFilters, + setUrlStatusesFilters, + }; }; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.test.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.test.ts new file mode 100644 index 0000000000000..376adee67bb36 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.test.ts @@ -0,0 +1,109 @@ +/* + * 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 { actionsLogFiltersFromUrlParams } from './use_action_history_url_params'; + +describe('#actionsLogFiltersFromUrlParams', () => { + it('should not use invalid command values to URL params', () => { + expect(actionsLogFiltersFromUrlParams({ commands: 'asa,was' })).toEqual({ + commands: undefined, + endDate: undefined, + hosts: undefined, + startDate: undefined, + statuses: undefined, + users: undefined, + }); + }); + + it('should use valid command values to URL params', () => { + expect( + actionsLogFiltersFromUrlParams({ + commands: 'kill-process,isolate,processes,release,suspend-process', + }) + ).toEqual({ + commands: ['isolate', 'kill-process', 'processes', 'release', 'suspend-process'], + endDate: undefined, + hosts: undefined, + startDate: undefined, + statuses: undefined, + users: undefined, + }); + }); + + it('should not use invalid status values to URL params', () => { + expect(actionsLogFiltersFromUrlParams({ statuses: 'asa,was' })).toEqual({ + commands: undefined, + endDate: undefined, + hosts: undefined, + startDate: undefined, + statuses: undefined, + users: undefined, + }); + }); + + it('should use valid status values to URL params', () => { + expect( + actionsLogFiltersFromUrlParams({ + statuses: 'successful,pending,failed', + }) + ).toEqual({ + commands: undefined, + endDate: undefined, + hosts: undefined, + startDate: undefined, + statuses: ['failed', 'pending', 'successful'], + users: undefined, + }); + }); + + it('should use valid command and status values to URL params', () => { + expect( + actionsLogFiltersFromUrlParams({ + commands: 'release,kill-process,isolate,processes,suspend-process', + statuses: 'successful,pending,failed', + }) + ).toEqual({ + commands: ['isolate', 'kill-process', 'processes', 'release', 'suspend-process'], + endDate: undefined, + hosts: undefined, + startDate: undefined, + statuses: ['failed', 'pending', 'successful'], + users: undefined, + }); + }); + + it('should use set given relative startDate and endDate values to URL params', () => { + expect( + actionsLogFiltersFromUrlParams({ + startDate: 'now-24h/h', + endDate: 'now', + }) + ).toEqual({ + commands: undefined, + endDate: 'now', + hosts: undefined, + startDate: 'now-24h/h', + statuses: undefined, + users: undefined, + }); + }); + + it('should use set given absolute startDate and endDate values to URL params', () => { + expect( + actionsLogFiltersFromUrlParams({ + startDate: '2022-09-12T08:00:00.000Z', + endDate: '2022-09-12T08:30:33.140Z', + }) + ).toEqual({ + commands: undefined, + endDate: '2022-09-12T08:30:33.140Z', + hosts: undefined, + startDate: '2022-09-12T08:00:00.000Z', + statuses: undefined, + users: undefined, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts new file mode 100644 index 0000000000000..24d728bd5c189 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts @@ -0,0 +1,194 @@ +/* + * 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, useEffect, useMemo, useState } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; +import { + RESPONSE_ACTION_COMMANDS, + RESPONSE_ACTION_STATUS, + type ResponseActions, + type ResponseActionStatus, +} from '../../../../../common/endpoint/service/response_actions/constants'; +import { useUrlParams } from '../../../hooks/use_url_params'; + +interface UrlParamsActionsLogFilters { + commands: string; + hosts: string; + statuses: string; + startDate: string; + endDate: string; + users: string; +} + +interface ActionsLogFiltersFromUrlParams { + commands?: Array< + Exclude | 'release' | 'processes' + >; + hosts?: string[]; + statuses?: ResponseActionStatus[]; + startDate?: string; + endDate?: string; + setUrlActionsFilters: (commands: UrlParamsActionsLogFilters['commands']) => void; + setUrlDateRangeFilters: ({ startDate, endDate }: { startDate: string; endDate: string }) => void; + setUrlHostsFilters: (agentIds: UrlParamsActionsLogFilters['hosts']) => void; + setUrlStatusesFilters: (statuses: UrlParamsActionsLogFilters['statuses']) => void; + setUrlUsersFilters: (users: UrlParamsActionsLogFilters['users']) => void; + users?: string[]; +} + +type FiltersFromUrl = Pick< + ActionsLogFiltersFromUrlParams, + 'commands' | 'hosts' | 'statuses' | 'users' | 'startDate' | 'endDate' +>; + +export const actionsLogFiltersFromUrlParams = ( + urlParams: Partial +): FiltersFromUrl => { + const actionsLogFilters: FiltersFromUrl = { + commands: [], + hosts: [], + statuses: [], + startDate: 'now-24h/h', + endDate: 'now', + users: [], + }; + + const urlCommands = urlParams.commands + ? String(urlParams.commands) + .split(',') + .reduce['commands']>((acc, curr) => { + if ( + RESPONSE_ACTION_COMMANDS.includes(curr as ResponseActions) || + curr === 'release' || + curr === 'processes' + ) { + acc.push(curr as Required['commands'][number]); + } + return acc.sort(); + }, []) + : []; + + const urlHosts = urlParams.hosts ? String(urlParams.hosts).split(',').sort() : []; + + const urlStatuses = urlParams.statuses + ? (String(urlParams.statuses).split(',') as ResponseActionStatus[]).reduce< + ResponseActionStatus[] + >((acc, curr) => { + if (RESPONSE_ACTION_STATUS.includes(curr)) { + acc.push(curr); + } + return acc.sort(); + }, []) + : []; + + const urlUsers = urlParams.hosts ? String(urlParams.users).split(',').sort() : []; + + actionsLogFilters.commands = urlCommands.length ? urlCommands : undefined; + actionsLogFilters.hosts = urlHosts.length ? urlHosts : undefined; + actionsLogFilters.statuses = urlStatuses.length ? urlStatuses : undefined; + actionsLogFilters.startDate = urlParams.startDate ? String(urlParams.startDate) : undefined; + actionsLogFilters.endDate = urlParams.endDate ? String(urlParams.endDate) : undefined; + actionsLogFilters.users = urlUsers.length ? urlUsers : undefined; + + return actionsLogFilters; +}; + +export const useActionHistoryUrlParams = (): ActionsLogFiltersFromUrlParams => { + // track actions and status filters + const location = useLocation(); + const history = useHistory(); + const { urlParams, toUrlParams } = useUrlParams(); + + const getUrlActionsLogFilters = useMemo( + () => actionsLogFiltersFromUrlParams(urlParams), + [urlParams] + ); + const [actionsLogFilters, setActionsLogFilters] = useState(getUrlActionsLogFilters); + + const setUrlActionsFilters = useCallback( + (commands: string) => { + history.push({ + ...location, + search: toUrlParams({ + ...urlParams, + commands: commands.length ? commands : undefined, + }), + }); + }, + [history, location, toUrlParams, urlParams] + ); + + const setUrlHostsFilters = useCallback( + (agentIds: string) => { + history.push({ + ...location, + search: toUrlParams({ + ...urlParams, + hosts: agentIds.length ? agentIds : undefined, + }), + }); + }, + [history, location, toUrlParams, urlParams] + ); + + const setUrlStatusesFilters = useCallback( + (statuses: string) => { + history.push({ + ...location, + search: toUrlParams({ + ...urlParams, + statuses: statuses.length ? statuses : undefined, + }), + }); + }, + [history, location, toUrlParams, urlParams] + ); + + const setUrlUsersFilters = useCallback( + (users: string) => { + history.push({ + ...location, + search: toUrlParams({ + ...urlParams, + users: users.length ? users : undefined, + }), + }); + }, + [history, location, toUrlParams, urlParams] + ); + + const setUrlDateRangeFilters = useCallback( + ({ startDate, endDate }: { startDate: string; endDate: string }) => { + history.push({ + ...location, + search: toUrlParams({ + ...urlParams, + startDate: startDate.length ? startDate : undefined, + endDate: endDate.length ? endDate : undefined, + }), + }); + }, + [history, location, toUrlParams, urlParams] + ); + + useEffect(() => { + setActionsLogFilters((prevState) => { + return { + ...prevState, + ...actionsLogFiltersFromUrlParams(urlParams), + }; + }); + }, [setActionsLogFilters, urlParams]); + + return { + ...actionsLogFilters, + setUrlActionsFilters, + setUrlDateRangeFilters, + setUrlHostsFilters, + setUrlStatusesFilters, + setUrlUsersFilters, + }; +}; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/mocks.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/mocks.tsx new file mode 100644 index 0000000000000..65b40d9a924ea --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/mocks.tsx @@ -0,0 +1,75 @@ +/* + * 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 uuid from 'uuid'; +import type { ActionListApiResponse } from '../../../../common/endpoint/types'; +import type { ResponseActionStatus } from '../../../../common/endpoint/service/response_actions/constants'; +import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; + +export const getActionListMock = async ({ + agentIds: _agentIds, + commands, + actionCount = 0, + endDate, + page = 1, + pageSize = 10, + startDate, + userIds, + isCompleted = true, + isExpired = false, + wasSuccessful = true, + status = 'successful', +}: { + agentIds?: string[]; + commands?: string[]; + actionCount?: number; + endDate?: string; + page?: number; + pageSize?: number; + startDate?: string; + userIds?: string[]; + isCompleted?: boolean; + isExpired?: boolean; + wasSuccessful?: boolean; + status?: ResponseActionStatus; +}): Promise => { + const endpointActionGenerator = new EndpointActionGenerator('seed'); + + const agentIds = _agentIds ?? [uuid.v4()]; + + const data: ActionListApiResponse['data'] = agentIds.map((id) => { + const actionIds = Array(actionCount) + .fill(1) + .map(() => uuid.v4()); + + const actionDetails: ActionListApiResponse['data'] = actionIds.map((actionId) => { + return endpointActionGenerator.generateActionDetails({ + agents: [id], + id: actionId, + isCompleted, + isExpired, + wasSuccessful, + status, + completedAt: isExpired ? undefined : new Date().toISOString(), + }); + }); + return actionDetails; + })[0]; + + return { + page, + pageSize, + startDate, + endDate, + elasticAgentIds: agentIds, + commands, + data, + userIds, + statuses: undefined, + total: data.length ?? 0, + }; +}; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx index 1f5e39c532a6f..64910663ac5cf 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx @@ -5,18 +5,18 @@ * 2.0. */ -import uuid from 'uuid'; import React from 'react'; import * as reactTestingLibrary from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; -import type { AppContextTestRender } from '../../../common/mock/endpoint'; -import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import { + createAppRootMockRenderer, + type AppContextTestRender, +} from '../../../common/mock/endpoint'; import { ResponseActionsLog } from './response_actions_log'; import type { ActionListApiResponse } from '../../../../common/endpoint/types'; -import type { ResponseActionStatus } from '../../../../common/endpoint/service/response_actions/constants'; import { MANAGEMENT_PATH } from '../../../../common/constants'; -import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; +import { getActionListMock } from './mocks'; let mockUseGetEndpointActionList: { isFetched?: boolean; @@ -500,69 +500,28 @@ describe('Response Actions Log', () => { expect(clearAllButton.hasAttribute('disabled')).toBeTruthy(); }); }); -}); -// mock API response -const getActionListMock = async ({ - agentIds: _agentIds, - commands, - actionCount = 0, - endDate, - page = 1, - pageSize = 10, - startDate, - userIds, - isCompleted = true, - isExpired = false, - wasSuccessful = true, - status = 'successful', -}: { - agentIds?: string[]; - commands?: string[]; - actionCount?: number; - endDate?: string; - page?: number; - pageSize?: number; - startDate?: string; - userIds?: string[]; - isCompleted?: boolean; - isExpired?: boolean; - wasSuccessful?: boolean; - status?: ResponseActionStatus; -}): Promise => { - const endpointActionGenerator = new EndpointActionGenerator('seed'); - - const agentIds = _agentIds ?? [uuid.v4()]; - - const data: ActionListApiResponse['data'] = agentIds.map((id) => { - const actionIds = Array(actionCount) - .fill(1) - .map(() => uuid.v4()); - - const actionDetails: ActionListApiResponse['data'] = actionIds.map((actionId) => { - return endpointActionGenerator.generateActionDetails({ - agents: [id], - id: actionId, - isCompleted, - isExpired, - wasSuccessful, - status, - completedAt: isExpired ? undefined : new Date().toISOString(), - }); + describe('Statuses filter', () => { + const filterPrefix = '-statuses-filter'; + + it('should show a list of statuses when opened', () => { + render(); + userEvent.click(renderResult.getByTestId(`${testPrefix}${filterPrefix}-popoverButton`)); + const filterList = renderResult.getByTestId(`${testPrefix}${filterPrefix}-popoverList`); + expect(filterList).toBeTruthy(); + expect(filterList.querySelectorAll('ul>li').length).toEqual(3); + expect( + Array.from(filterList.querySelectorAll('ul>li')).map((option) => option.textContent) + ).toEqual(['Failed', 'Pending', 'Successful']); }); - return actionDetails; - })[0]; - return { - page, - pageSize, - startDate, - endDate, - elasticAgentIds: agentIds, - commands, - data, - userIds, - statuses: undefined, - total: data.length ?? 0, - }; -}; + it('should have `clear all` button `disabled` when no selected values', () => { + render(); + userEvent.click(renderResult.getByTestId(`${testPrefix}${filterPrefix}-popoverButton`)); + const clearAllButton = renderResult.getByTestId( + `${testPrefix}${filterPrefix}-clearAllButton` + ); + expect(clearAllButton.hasAttribute('disabled')).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx index d12fce4efcb95..79c99d62e2ff6 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx @@ -25,7 +25,7 @@ import { import { euiStyled, css } from '@kbn/kibana-react-plugin/common'; import type { HorizontalAlignment, CriteriaWithPagination } from '@elastic/eui'; -import React, { memo, useCallback, useMemo, useState } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import type { ResponseActions, @@ -41,8 +41,15 @@ import { OUTPUT_MESSAGES, TABLE_COLUMN_NAMES, UX_MESSAGES } from './translations import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../common/constants'; import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; import { ActionsLogFilters } from './components/actions_log_filters'; -import { getActionStatus, getCommand, useDateRangePicker } from './components/hooks'; +import { + getActionStatus, + getUiCommand, + getCommandKey, + useDateRangePicker, +} from './components/hooks'; import { StatusBadge } from './components/status_badge'; +import { useActionHistoryUrlParams } from './components/use_action_history_url_params'; +import { useUrlPagination } from '../../hooks/use_url_pagination'; const emptyValue = getEmptyValue(); @@ -104,24 +111,48 @@ const StyledEuiCodeBlock = euiStyled(EuiCodeBlock).attrs({ `; export const ResponseActionsLog = memo< - Pick & { showHostNames?: boolean } ->(({ agentIds, showHostNames = false }) => { + Pick & { showHostNames?: boolean; isFlyout?: boolean } +>(({ agentIds, showHostNames = false, isFlyout = true }) => { + const { pagination: paginationFromUrlParams, setPagination: setPaginationOnUrlParams } = + useUrlPagination(); + const { + commands: commandsFromUrl, + statuses: statusesFromUrl, + startDate: startDateFromUrl, + endDate: endDateFromUrl, + } = useActionHistoryUrlParams(); + const getTestId = useTestIdGenerator('response-actions-list'); const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<{ [k: ActionListApiResponse['data'][number]['id']]: React.ReactNode; }>({}); const [queryParams, setQueryParams] = useState({ - page: 1, - pageSize: 10, + page: isFlyout ? 1 : paginationFromUrlParams.page, + pageSize: isFlyout ? 10 : paginationFromUrlParams.pageSize, agentIds, commands: [], statuses: [], userIds: [], }); + // update query state from URL params + useEffect(() => { + if (!isFlyout) { + setQueryParams((prevState) => ({ + ...prevState, + commands: commandsFromUrl?.length + ? commandsFromUrl.map((commandFromUrl) => getCommandKey(commandFromUrl)) + : prevState.commands, + statuses: statusesFromUrl?.length + ? (statusesFromUrl as ResponseActionStatus[]) + : prevState.statuses, + })); + } + }, [commandsFromUrl, isFlyout, statusesFromUrl, setQueryParams]); + // date range picker state and handlers - const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker(); + const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker(isFlyout); // initial fetch of list data const { @@ -132,8 +163,8 @@ export const ResponseActionsLog = memo< refetch: reFetchEndpointActionList, } = useGetEndpointActionList({ ...queryParams, - startDate: dateRangePickerState.startDate, - endDate: dateRangePickerState.endDate, + startDate: isFlyout ? dateRangePickerState.startDate : startDateFromUrl, + endDate: isFlyout ? dateRangePickerState.endDate : endDateFromUrl, }); // handle auto refresh data @@ -191,7 +222,7 @@ export const ResponseActionsLog = memo< }) : undefined; - const command = getCommand(_command); + const command = getUiCommand(_command); const dataList = [ { title: OUTPUT_MESSAGES.expandSection.placedAt, @@ -294,7 +325,7 @@ export const ResponseActionsLog = memo< width: !showHostNames ? '21%' : '10%', truncateText: true, render: (_command: ActionListApiResponse['data'][number]['command']) => { - const command = getCommand(_command); + const command = getUiCommand(_command); return ( { + const pageIndex = isFlyout ? (queryParams.page || 1) - 1 : paginationFromUrlParams.page - 1; + const pageSize = isFlyout ? queryParams.pageSize || 10 : paginationFromUrlParams.pageSize; return { // this controls the table UI page // to match 0-based table paging - pageIndex: (queryParams.page || 1) - 1, - pageSize: queryParams.pageSize || 10, + pageIndex, + pageSize, totalItemCount, pageSizeOptions: MANAGEMENT_PAGE_SIZE_OPTIONS as number[], }; - }, [queryParams, totalItemCount]); + }, [ + isFlyout, + paginationFromUrlParams.page, + paginationFromUrlParams.pageSize, + queryParams.page, + queryParams.pageSize, + totalItemCount, + ]); // handle onChange const handleTableOnChange = useCallback( ({ page: _page }: CriteriaWithPagination) => { // table paging is 0 based const { index, size } = _page; - setQueryParams((prevState) => ({ - ...prevState, - // adjust the page to conform to - // 1-based API page + // adjust the page to conform to + // 1-based API page + const pagingArgs = { page: index + 1, pageSize: size, - })); + }; + if (isFlyout) { + setQueryParams((prevState) => ({ + ...prevState, + ...pagingArgs, + })); + } else { + setQueryParams((prevState) => ({ + ...prevState, + ...pagingArgs, + })); + setPaginationOnUrlParams({ + ...pagingArgs, + }); + } reFetchEndpointActionList(); }, - [reFetchEndpointActionList, setQueryParams] + [isFlyout, reFetchEndpointActionList, setQueryParams, setPaginationOnUrlParams] ); // compute record ranges @@ -516,6 +569,7 @@ export const ResponseActionsLog = memo< return ( <> unknown; +}; +jest.mock('../../../hooks/endpoint/use_get_endpoint_action_list', () => { + const original = jest.requireActual('../../../hooks/endpoint/use_get_endpoint_action_list'); + return { + ...original, + useGetEndpointActionList: () => mockUseGetEndpointActionList, + }; +}); + +jest.mock('@kbn/kibana-react-plugin/public', () => { + const original = jest.requireActual('@kbn/kibana-react-plugin/public'); + return { + ...original, + useKibana: () => ({ + services: { + uiSettings: { + get: jest.fn().mockImplementation((key) => { + const get = (k: 'dateFormat' | 'timepicker:quickRanges') => { + const x = { + dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', + 'timepicker:quickRanges': [ + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + { + from: 'now/w', + to: 'now/w', + display: 'This week', + }, + { + from: 'now-15m', + to: 'now', + display: 'Last 15 minutes', + }, + { + from: 'now-30m', + to: 'now', + display: 'Last 30 minutes', + }, + { + from: 'now-1h', + to: 'now', + display: 'Last 1 hour', + }, + { + from: 'now-24h', + to: 'now', + display: 'Last 24 hours', + }, + { + from: 'now-7d', + to: 'now', + display: 'Last 7 days', + }, + { + from: 'now-30d', + to: 'now', + display: 'Last 30 days', + }, + { + from: 'now-90d', + to: 'now', + display: 'Last 90 days', + }, + { + from: 'now-1y', + to: 'now', + display: 'Last 1 year', + }, + ], + }; + return x[k]; + }; + return get(key); + }), + }, + }, + }), + }; +}); + +describe('Action history page', () => { + const testPrefix = 'response-actions-list'; + + let render: () => ReturnType; + let renderResult: ReturnType; + let history: AppContextTestRender['history']; + let mockedContext: AppContextTestRender; + + const refetchFunction = jest.fn(); + const baseMockedActionList = { + isFetched: true, + isFetching: false, + error: null, + refetch: refetchFunction, + }; + + beforeEach(async () => { + mockedContext = createAppRootMockRenderer(); + ({ history } = mockedContext); + render = () => (renderResult = mockedContext.render()); + reactTestingLibrary.act(() => { + history.push(`${MANAGEMENT_PATH}/response_actions`); + }); + + mockUseGetEndpointActionList = { + ...baseMockedActionList, + data: await getActionListMock({ actionCount: 43 }), + }; + }); + + afterEach(() => { + mockUseGetEndpointActionList = { + ...baseMockedActionList, + }; + jest.clearAllMocks(); + }); + + describe('Read from URL params', () => { + it('should read and set paging values from URL params', () => { + reactTestingLibrary.act(() => { + history.push('/administration/action_history?page=3&pageSize=20'); + }); + render(); + const { getByTestId } = renderResult; + + expect(history.location.search).toEqual('?page=3&pageSize=20'); + expect(getByTestId('tablePaginationPopoverButton').textContent).toContain('20'); + expect(getByTestId('pagination-button-2').getAttribute('aria-current')).toStrictEqual('true'); + }); + + it('should read and set command filter values from URL params', () => { + const filterPrefix = 'actions-filter'; + reactTestingLibrary.act(() => { + history.push('/administration/action_history?commands=release,processes'); + }); + + render(); + const { getAllByTestId, getByTestId } = renderResult; + userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`)); + const allFilterOptions = getAllByTestId(`${filterPrefix}-option`); + + const selectedFilterOptions = allFilterOptions.reduce((acc, option) => { + if (option.getAttribute('aria-checked') === 'true') { + acc.push(option.textContent?.split('-')[0].trim() as string); + } + return acc; + }, []); + + expect(selectedFilterOptions.length).toEqual(2); + expect(selectedFilterOptions).toEqual(['release', 'processes']); + expect(history.location.search).toEqual('?commands=release,processes'); + }); + + it('should read and set status filter values from URL params', () => { + const filterPrefix = 'statuses-filter'; + reactTestingLibrary.act(() => { + history.push('/administration/action_history?statuses=pending,failed'); + }); + + render(); + const { getAllByTestId, getByTestId } = renderResult; + userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`)); + const allFilterOptions = getAllByTestId(`${filterPrefix}-option`); + + const selectedFilterOptions = allFilterOptions.reduce((acc, option) => { + if (option.getAttribute('aria-checked') === 'true') { + acc.push(option.textContent?.split('-')[0].trim() as string); + } + return acc; + }, []); + + expect(selectedFilterOptions.length).toEqual(2); + expect(selectedFilterOptions).toEqual(['Failed', 'Pending']); + expect(history.location.search).toEqual('?statuses=pending,failed'); + }); + + // TODO: add tests for hosts and users when those filters are added + + it('should read and set relative date ranges filter values from URL params', () => { + reactTestingLibrary.act(() => { + history.push('/administration/action_history?startDate=now-23m&endDate=now-1m'); + }); + + render(); + const { getByTestId } = renderResult; + + expect(getByTestId('superDatePickerstartDatePopoverButton').textContent).toEqual( + '~ 23 minutes ago' + ); + expect(getByTestId('superDatePickerendDatePopoverButton').textContent).toEqual( + '~ a minute ago' + ); + expect(history.location.search).toEqual('?startDate=now-23m&endDate=now-1m'); + }); + + it('should read and set absolute date ranges filter values from URL params', () => { + const startDate = '2022-09-12T11:00:00.000Z'; + const endDate = '2022-09-12T11:30:33.000Z'; + reactTestingLibrary.act(() => { + history.push(`/administration/action_history?startDate=${startDate}&endDate=${endDate}`); + }); + + const { getByTestId } = render(); + + expect(getByTestId('superDatePickerstartDatePopoverButton').textContent).toEqual( + 'Sep 12, 2022 @ 07:00:00.000' + ); + expect(getByTestId('superDatePickerendDatePopoverButton').textContent).toEqual( + 'Sep 12, 2022 @ 07:30:33.000' + ); + expect(history.location.search).toEqual(`?startDate=${startDate}&endDate=${endDate}`); + }); + }); + + describe('Set selected/set values to URL params', () => { + it('should set selected page number to URL params', () => { + render(); + const { getByTestId } = renderResult; + + userEvent.click(getByTestId('pagination-button-1')); + expect(history.location.search).toEqual('?page=2&pageSize=10'); + }); + + it('should set selected pageSize value to URL params', () => { + render(); + const { getByTestId } = renderResult; + + userEvent.click(getByTestId('tablePaginationPopoverButton')); + const pageSizeOption = getByTestId('tablePagination-20-rows'); + pageSizeOption.style.pointerEvents = 'all'; + userEvent.click(pageSizeOption); + + expect(history.location.search).toEqual('?page=1&pageSize=20'); + }); + + it('should set selected command filter options to URL params ', () => { + const filterPrefix = 'actions-filter'; + render(); + const { getAllByTestId, getByTestId } = renderResult; + userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`)); + const allFilterOptions = getAllByTestId(`${filterPrefix}-option`); + + allFilterOptions.forEach((option) => { + option.style.pointerEvents = 'all'; + userEvent.click(option); + }); + + expect(history.location.search).toEqual( + '?commands=isolate%2Crelease%2Ckill-process%2Csuspend-process%2Cprocesses' + ); + }); + + it('should set selected status filter options to URL params ', () => { + const filterPrefix = 'statuses-filter'; + render(); + const { getAllByTestId, getByTestId } = renderResult; + userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`)); + const allFilterOptions = getAllByTestId(`${filterPrefix}-option`); + + allFilterOptions.forEach((option) => { + option.style.pointerEvents = 'all'; + userEvent.click(option); + }); + + expect(history.location.search).toEqual('?statuses=failed%2Cpending%2Csuccessful'); + }); + + // TODO: add tests for hosts and users when those filters are added + + it('should set selected relative date range filter options to URL params ', async () => { + const { getByTestId } = render(); + const quickMenuButton = getByTestId('superDatePickerToggleQuickMenuButton'); + const startDatePopoverButton = getByTestId(`superDatePickerShowDatesButton`); + + // shows 24 hours at first + expect(startDatePopoverButton).toHaveTextContent('Last 24 hours'); + + // pick another relative date + userEvent.click(quickMenuButton); + await waitForEuiPopoverOpen(); + userEvent.click(getByTestId('superDatePickerCommonlyUsed_Last_15 minutes')); + expect(startDatePopoverButton).toHaveTextContent('Last 15 minutes'); + + expect(history.location.search).toEqual('?endDate=now&startDate=now-15m'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx index 23b3da831ddac..2b8d21f8f0ac1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx @@ -18,7 +18,7 @@ export const ResponseActionsListPage = () => { title={ACTION_HISTORY} subtitle={UX_MESSAGES.pageSubTitle} > - + ); }; From efbf9e4d31c7fea741749df0abb28107aed4d16b Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Tue, 13 Sep 2022 07:35:21 -0700 Subject: [PATCH 17/28] [DOCS] Augment create and run connector APIs with ServiceNow details (#140348) --- .../actions-and-connectors/create.asciidoc | 85 ++++- .../actions-and-connectors/execute.asciidoc | 350 ++++++++++++++++-- x-pack/plugins/actions/README.md | 11 +- 3 files changed, 420 insertions(+), 26 deletions(-) diff --git a/docs/api/actions-and-connectors/create.asciidoc b/docs/api/actions-and-connectors/create.asciidoc index e8533e0561cce..c33cbdd77232c 100644 --- a/docs/api/actions-and-connectors/create.asciidoc +++ b/docs/api/actions-and-connectors/create.asciidoc @@ -83,6 +83,41 @@ For more information, refer to <>. For more information, refer to <>. ===== +.{sn-itom}, {sn-itsm}, and {sn-sir} connectors +[%collapsible%open] +===== +`apiUrl`:: +(Required, string) The {sn} instance URL. + +`clientId`:: +(Required^*^, string) The client ID assigned to your OAuth application. This +property is required when `isOAuth` is `true`. + +`isOAuth`:: +(Optional, string) The type of authentication to use. The default value is +`false`, which means basic authentication is used instead of open authorization +(OAuth). + +`jwtKeyId`:: +(Required^*^, string) The key identifier assigned to the JWT verifier map of +your OAuth application. This property is required when `isOAuth` is `true`. + +`userIdentifierValue`:: +(Required^*^, string) The identifier to use for OAuth authentication. This +identifier should be the user field you selected when you created an OAuth +JWT API endpoint for external clients in your {sn} instance. For example, if +the selected user field is `Email`, the user identifier should be the user's +email address. This property is required when `isOAuth` is `true`. + +`usesTableApi`:: +(Optional, string) Determines whether the connector uses the Table API or the +Import Set API. This property is supported only for {sn-itsm} and {sn-sir} +connectors. ++ +NOTE: If this property is set to false, the Elastic application should be +installed in {sn}. +===== + .{swimlane} connectors [%collapsible%open] ===== @@ -373,7 +408,7 @@ For more configuration properties, refer to <>. `connector_type_id`:: (Required, string) The connector type ID for the connector. For example, -`.cases-webhook`, `.index`, `.jira`, or `.server-log`. +`.cases-webhook`, `.index`, `.jira`, `.server-log`, or `.servicenow-itom`. `name`:: (Required, string) The display name for the connector. @@ -412,6 +447,31 @@ authentication. (Required, string) The account email for HTTP Basic authentication. ===== +.{sn-itom}, {sn-itsm}, and {sn-sir} connectors +[%collapsible%open] +===== +`clientSecret`:: +(Required^*^, string) The client secret assigned to your OAuth application. This +property is required when `isOAuth` is `true`. + +`password`:: +(Required^*^, string) The password for HTTP basic authentication. This property +is required when `isOAuth` is `false`. + +`privateKey`:: +(Required^*^, string) The RSA private key that you created for use in {sn}. This +property is required when `isOAuth` is `true`. + +privateKeyPassword:: +(Required^*^, string) The password for the RSA private key. This property is +required when `isOAuth` is `true` and you set a password on your private key. + +`username`:: +(Required^*^, string) The username for HTTP basic authentication. This property +is required when `isOAuth` is `false`. + +===== + .{swimlane} connectors [%collapsible%open] ===== @@ -516,6 +576,29 @@ POST api/actions/connector -------------------------------------------------- // KIBANA +Create an {sn-itom} connector that uses open authorization: + +[source,sh] +-------------------------------------------------- +POST api/actions/connector +{ + "name": "my-itom-connector", + "connector_type_id": ".servicenow-itom", + "config": { + "apiUrl": "https://exmaple.service-now.com/", + "clientId": "abcdefghijklmnopqrstuvwxyzabcdef", + "isOAuth": "true", + "jwtKeyId": "fedcbazyxwvutsrqponmlkjihgfedcba", + "userIdentifierValue": "testuser@email.com" + }, + "secrets": { + "clientSecret": "secretsecret", + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nprivatekeyhere\n-----END RSA PRIVATE KEY-----" + } +} +-------------------------------------------------- +// KIBANA + Create a {swimlane} connector: [source,sh] diff --git a/docs/api/actions-and-connectors/execute.asciidoc b/docs/api/actions-and-connectors/execute.asciidoc index 29e9bfa4cf224..e477d7237c273 100644 --- a/docs/api/actions-and-connectors/execute.asciidoc +++ b/docs/api/actions-and-connectors/execute.asciidoc @@ -50,19 +50,23 @@ depending on the connector type. For information about the parameter properties, refer to <>. + -- -.Index connectors +.`Params` properties [%collapsible%open] ==== + +.Index connectors +[%collapsible%open] +===== `documents`:: (Required, array of objects) The documents to index in JSON format. For more information, refer to {kibana-ref}/index-action-type.html[Index connector and action]. -==== +===== .Jira connectors [%collapsible%open] -==== +===== `subAction`:: (Required, string) The action to test. Valid values include: `fieldsByIssueType`, `getFields`, `getIncident`, `issue`, `issues`, `issueTypes`, and `pushToService`. @@ -74,55 +78,55 @@ on the `subAction` value. This object is not required when `subAction` is + .Properties when `subAction` is `fieldsByIssueType` [%collapsible%open] -===== +====== `id`::: (Required, string) The Jira issue type identifier. For example, `10024`. -===== +====== + .Properties when `subAction` is `getIncident` [%collapsible%open] -===== +====== `externalId`::: (Required, string) The Jira issue identifier. For example, `71778`. -===== +====== + .Properties when `subAction` is `issue` [%collapsible%open] -===== +====== `id`::: (Required, string) The Jira issue identifier. For example, `71778`. -===== +====== + .Properties when `subAction` is `issues` [%collapsible%open] -===== +====== `title`::: (Required, string) The title of the Jira issue. -===== +====== + .Properties when `subAction` is `pushToService` [%collapsible%open] -===== -comments::: +====== +`comments`::: (Optional, array of objects) Additional information that is sent to Jira. + .Properties of `comments` [%collapsible%open] -====== -comment:::: +======= +`comment`:::: (string) A comment related to the incident. For example, describe how to troubleshoot the issue. -commentId:::: +`commentId`:::: (integer) A unique identifier for the comment. -====== +======= -incident::: +`incident`::: (Required, object) Information necessary to create or update a Jira incident. + .Properties of `incident` [%collapsible%open] -====== +======= `description`:::: (Optional, string) The details about the incident. @@ -151,22 +155,288 @@ types of issues. `title`:::: (Optional, string) A title for the incident, used for searching the contents of the knowledge base. +======= ====== -===== For more information, refer to {kibana-ref}/jira-action-type.html[{jira} connector and action]. -==== +===== + +.{sn-itom} connectors +[%collapsible%open] +===== +`subAction`:: +(Required, string) The action to test. Valid values include: `addEvent` and +`getChoices`. + +`subActionParams`:: +(Required^*^, object) The set of configuration properties, which vary depending +on the `subAction` value. ++ +.Properties when `subAction` is `addEvent` +[%collapsible%open] +====== +`additional_info`:::: +(Optional, string) Additional information about the event. + +`description`:::: +(Optional, string) The details about the event. + +`event_class`:::: +(Optional, string) A specific instance of the source. + +`message_key`:::: +(Optional, string) All actions sharing this key are associated with the same +{sn} alert. The default value is `:`. + +`metric_name`:::: +(Optional, string) The name of the metric. + +`node`:::: +(Optional, string) The host that the event was triggered for. + +`resource`:::: +(Optional, string) The name of the resource. + +`severity`:::: +(Optional, string) The severity of the event. + +`source`:::: +(Optional, string) The name of the event source type. + +`time_of_event`:::: +(Optional, string) The time of the event. + +`type`:::: +(Optional, string) The type of event. +====== ++ +.Properties when `subAction` is `getChoices` +[%collapsible%open] +====== +`fields`:::: +(Required, array of strings) An array of fields. For example, `["severity"]`. +====== +===== + +.{sn-itsm} connectors +[%collapsible%open] +===== +`subAction`:: +(Required, string) The action to test. Valid values include: `getFields`, +`getIncident`, `getChoices`, and `pushToService`. + +`subActionParams`:: +(Required^*^, object) The set of configuration properties, which vary depending +on the `subAction` value. This object is not required when `subAction` is +`getFields`. ++ +.Properties when `subAction` is `getChoices` +[%collapsible%open] +====== +`fields`:::: +(Required, array of strings) An array of fields. For example, `["category","impact"]`. +====== ++ +.Properties when `subAction` is `getIncident` +[%collapsible%open] +====== +`externalId`:::: +(Required, string) The {sn-itsm} issue identifier. +====== ++ +.Properties when `subAction` is `pushToService` +[%collapsible%open] +====== +`comments`::: +(Optional, array of objects) Additional information that is sent to {sn-sir}. ++ +.Properties of `comments` +[%collapsible%open] +======= +`comment`:::: +(string) A comment related to the incident. For example, describe how to +troubleshoot the issue. + +`commentId`:::: +(integer) A unique identifier for the comment. + +//// +version:::: +(string) TBD +//// +======= + +`incident`::: +(Required, object) Information necessary to create or update a {sn-sir} incident. ++ +.Properties of `incident` +[%collapsible%open] +======= +`category`:::: +(Optional, string) The category of the incident. + +`correlation_display`:::: +(Optional, string) A descriptive label of the alert for correlation purposes in +{sn}. + +`correlation_id`:::: +(Optional, string) The correlation identifier for the security incident. +Connectors using the same correlation ID are associated with the same {sn} +incident. This value determines whether a new {sn} incident is created or an +existing one is updated. Modifying this value is optional; if not modified, the +rule ID and alert ID are combined as `{{ruleID}}:{{alert ID}}` to form the +correlation ID value in {sn}. The maximum character length for this value is 100 +characters. ++ +NOTE: Using the default configuration of `{{ruleID}}:{{alert ID}}` ensures +that {sn} creates a separate incident record for every generated alert that uses +a unique alert ID. If the rule generates multiple alerts that use the same alert +IDs, {sn} creates and continually updates a single incident record for the alert. + +`description`:::: +(Optional, string) The details about the incident. + +`externalId`:::: +(Optional, string) The {sn-itsm} issue identifier. If present, the incident is +updated. Otherwise, a new incident is created. + +`impact`:::: +(Optional, string) The impact in {sn-itsm}. + +`severity`:::: +(Optional, string) The severity of the incident. + +`short_description`:::: +(Required, string) A short description for the incident, used for searching the +contents of the knowledge base. + +`subcategory`:::: +(Optional, string) The subcategory in {sn-itsm}. + +`urgency`:::: +(Optional, string) The urgency in {sn-itsm}. +======= +====== +===== + +.{sn-sir} connectors +[%collapsible%open] +===== +`subAction`:: +(Required, string) The action to test. Valid values include: `getFields`, +`getIncident`, `getChoices`, and `pushToService`. + +`subActionParams`:: +(Required^*^, object) The set of configuration properties, which vary depending +on the `subAction` value. This object is not required when `subAction` is +`getFields`. ++ +.Properties when `subAction` is `getChoices` +[%collapsible%open] +====== +`fields`:::: +(Required, array of strings) An array of fields. For example, `["priority","category"]`. +====== ++ +.Properties when `subAction` is `getIncident` +[%collapsible%open] +====== +`externalId`:::: +(Required, string) The {sn-sir} issue identifier. +====== ++ +.Properties when `subAction` is `pushToService` +[%collapsible%open] +====== +`comments`::: +(Optional, array of objects) Additional information that is sent to {sn-sir}. ++ +.Properties of `comments` +[%collapsible%open] +======= +`comment`:::: +(string) A comment related to the incident. For example, describe how to +troubleshoot the issue. + +`commentId`:::: +(integer) A unique identifier for the comment. + +//// +`version`:::: +(string) TBD +//// +======= + +`incident`::: +(Required, object) Information necessary to create or update a {sn-sir} incident. ++ +.Properties of `incident` +[%collapsible%open] +======= +`category`:::: +(Optional, string) The category of the incident. + +`correlation_display`:::: +(Optional, string) A descriptive label of the alert for correlation purposes in +{sn}. + +`correlation_id`:::: +(Optional, string) The correlation identifier for the security incident. +Connectors using the same correlation ID are associated with the same {sn} +incident. This value determines whether a new {sn} incident is created or an +existing one is updated. Modifying this value is optional; if not modified, the +rule ID and alert ID are combined as `{{ruleID}}:{{alert ID}}` to form the +correlation ID value in {sn}. The maximum character length for this value is 100 +characters. ++ +NOTE: Using the default configuration of `{{ruleID}}:{{alert ID}}` ensures that +{sn} creates a separate incident record for every generated alert that uses a +unique alert ID. If the rule generates multiple alerts that use the same alert +IDs, {sn} creates and continually updates a single incident record for the alert. + +`description`:::: +(Optional, string) The details about the incident. + +`dest_ip`:::: +(Optional, string or array of strings) A list of destination IP addresses related +to the security incident. The IPs are added as observables to the security incident. + +`externalId`:::: +(Optional, string) The {sn-sir} issue identifier. If present, the incident is +updated. Otherwise, a new incident is created. + +`malware_hash`:::: +(Optional, string or array of strings) A list of malware URLs related to the +security incident. The URLs are added as observables to the security incident. + +`priority`:::: +(Optional, string) The priority of the incident. + +`short_description`:::: +(Required, string) A short description for the incident, used for searching the +contents of the knowledge base. + +`source_ip`:::: +(Optional, string or array of strings) A list of source IP addresses related to +the security incident. The IPs are added as observables to the security incident. + +`subcategory`:::: +(Optional, string) The subcategory of the incident. +======= +====== +===== .Server log connectors [%collapsible%open] -==== +===== `level`:: (Optional, string) The log level of the message: `trace`, `debug`, `info`, `warn`, `error`, or `fatal`. Defaults to `info`. `message`:: (Required, string) The message to log. +===== ==== -- @@ -277,4 +547,40 @@ The API returns the following: ], "connector_id":"b3aad810-edbe-11ec-82d1-11348ecbf4a6" } +-------------------------------------------------- + +Retrieve the list of choices for a {sn-itom} connector: + +[source,sh] +-------------------------------------------------- +POST api/actions/connector/9d9be270-2fd2-11ed-b0e0-87533c532698/_execute +{ + "params": { + "subAction": "getChoices", + "subActionParams": { + "fields": [ "severity","urgency" ] + } + } +} +-------------------------------------------------- +// KIBANA + +The API returns the severity and urgency choices, for example: + +[source,sh] +-------------------------------------------------- +{ + "status": "ok", + "data":[ + {"dependent_value":"","label":"Critical","value":"1","element":"severity"}, + {"dependent_value":"","label":"Major","value":"2","element":"severity"}, + {"dependent_value":"","label":"Minor","value":"3","element":"severity"}, + {"dependent_value":"","label":"Warning","value":"4","element":"severity"}, + {"dependent_value":"","label":"OK","value":"5","element":"severity"}, + {"dependent_value":"","label":"Clear","value":"0","element":"severity"}, + {"dependent_value":"","label":"1 - High","value":"1","element":"urgency"}, + {"dependent_value":"","label":"2 - Medium","value":"2","element":"urgency"}, + {"dependent_value":"","label":"3 - Low","value":"3","element":"urgency"}], + "connector_id":"9d9be270-2fd2-11ed-b0e0-87533c532698" +} -------------------------------------------------- \ No newline at end of file diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index 7d9dc9d406035..213ec6d8f23ce 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -260,7 +260,9 @@ In addition to the documented configurations, several built in action type offer ## ServiceNow ITSM -The [ServiceNow ITSM user documentation `params`](https://www.elastic.co/guide/en/kibana/master/servicenow-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available. +Refer to the [Run connector API documentation](https://www.elastic.co/guide/en/kibana/master/execute-connector-api.html#execute-connector-api-request-body) +for the full list of properties. + ### `params` | Property | Description | Type | @@ -311,7 +313,8 @@ No parameters for the `getFields` subaction. Provide an empty object `{}`. ## ServiceNow Sec Ops -The [ServiceNow SecOps user documentation `params`](https://www.elastic.co/guide/en/kibana/master/servicenow-sir-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available. +Refer to the [Run connector API documentation](https://www.elastic.co/guide/en/kibana/master/execute-connector-api.html#execute-connector-api-request-body) +for the full list of properties. ### `params` @@ -364,7 +367,9 @@ No parameters for the `getFields` subaction. Provide an empty object `{}`. --- ## ServiceNow ITOM -The [ServiceNow ITOM user documentation `params`](https://www.elastic.co/guide/en/kibana/master/servicenow-itom-action-type.html) lists configuration properties for the `addEvent` subaction. In addition, several other subaction types are available. +Refer to the [Run connector API documentation](https://www.elastic.co/guide/en/kibana/master/execute-connector-api.html#execute-connector-api-request-body) +for the full list of properties. + ### `params` | Property | Description | Type | From 1fcd75a70662b911c9d0e7edf675c4a2ddf9e921 Mon Sep 17 00:00:00 2001 From: Nodir Latipov Date: Tue, 13 Sep 2022 19:36:52 +0500 Subject: [PATCH 18/28] [Unified Search] Create a filter builder component which works with nested OR/AND filters (#136815) * init: add filters editor component * apply some changes * add storybook * push some changes * push some logic * push some logic * push some logic * add reducer * add more complext filters * feat: add moveFilter action * feat: add input form for filters editor filter item * feat: add filter UI * fix: storybook * fix UI: edit filter component * style: add panel to filter * feat: update style * style: add style for dilimeter * refact: filter item * style: added style for odd filter group * feat: added add filter action * feat: update add filter in filter group * feat: added remove filter * refact: updated add filter and remove filter * feat: added addFilterGroup * feat: updated addFilterGroup with empty filter * refact: utils * refact: seperte filters editor filter (field input) * refact: seperte filters editor filter (operator input) * refact: filters editor * refact: filters editor (params field * push some changes * push some cleanup * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * move filters to __mock__ folder * feat: update handle filter item * add getFilterByPath method * remove addFilterGroupWithFilter * remove addFilterGroupWithFilter * add some logic * add addFilter support * update styles * add removeFilter * fix types * refact: cleanup any * refact: filter item * fix: data for show filter item * refact: cleanup useState in filters editor * refact: filter edit * style: fix UI in filter group * add disableOR/AND/DELETE params * add preparation for d2d * fix: show filter item after add filter * update filterGroup * fix: drag and drop * do cleanup * feat: add logic for drag&drop * style: filter item * apply new structure * push some logic * fix some cases * feat: udpated updateFilter * feat: added update field in filter * feat: added update params in filter * feat: added update operator in filter * feat: added update operator in filter--amend * style: add uiPanel into filterItem * feat: added translate * refact: filter item * feat: updated filter item for exists type * feat: added unique key for filter Item * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * fix: update params * fix: update params * style: params input * fix: disabled for phrase value input * feat: added filter params update * refact: change type * feat: index for EuiDraggable * refact: rename properties * feat: added useCallback * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * refact: cleanup code * refact: minor changes * style: change show label for filter phrase(s) * dandd preparation * feat: add ref to panel * fix merge configct * refact: destination => dropTarget and added it into context * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * feat: udpate d&d logic * feat: added chagne cursor for OR filter * feat: add change cursor in d&d * feat: add change cursor in d&d * style: changed cursor view svg * feat: added disable or conditional in d&d * feat: moved filter_editor -> shared_component * style: used emotion react * move filter_label * rename filters editor -> filters builder * feat: added hideOr * feat: added hideOr * feat: added sisabled and drop if depth = maxDepth * feat: update disable isCombine * feat: rename filtersEditor->filtersBulder * feat: added new storybook * feat: remove autocomplete service * refact: updated filterBuilder * feat: updated css style * fix: jest test * refact: move snapshot for filter label * feat: update search bar * refact: filter builder utils * fix: update filter * refact: update filter builder * fix: autocomplete test * fix: test * fix: jest test * fix: d&d * test: update test * test: update test * fix: functional tests: * fix: check type * refact: move filter * refact: minor * feat: move filter editor from shared components to filter bar * minor update * fix: check type * fix: type check * fix: d&d * fix: d&d * feat: resolve comment * minor fix * fix: d&d * fix: type check test * fix: check type test * feat: covered new case with d&d * feat: covered new case with d&d * feat: covered new case with d&d * refact: move filter * fix: bug with storybook * refact: remove todo comment * fix normalizeFilters method * refact: ref to FilterEditor * refact: moved filters_builder to up level * fix: update operator * refact: key for list * refact: did FiltersBuilder lazy * feat: move cvg to under component and rename * feat: resolve comment * feat: added validation for filter build item into params enter uncorrect date * test: added test for normalizeFilters * feat: update test * style: fix UI * refact: utils files in filters builder * style: filter group background * style: filter group * doc: added comment to methods in utils * style: fix border * style: fix border * fix: UI * feat: added backgroud for gray filter group * temp changes * fix some styles * feat: added devider and margin * style: change style -> css * style: change style -> css * refact: style * refact: style * fix: test * refact: style * feat: added rule for show border * style: UI * style: show OR delimiter * style: margin for AND delimiter * feat: update show border for first level * feat: update styles * style: fix padding for panel --- src/plugins/data/public/index.ts | 1 - src/plugins/data/public/types.ts | 16 +- .../dataview_picker/change_dataview.tsx | 4 +- .../filter_editor/filter_editor.test.tsx | 5 +- .../{index.tsx => filter_editor.tsx} | 62 +- .../public/filter_bar/filter_editor/index.ts | 41 ++ .../filter_editor/lib/filter_editor_utils.ts | 2 +- .../filter_editor/lib}/helpers.test.ts | 0 .../filter_editor/lib}/helpers.ts | 4 +- .../filter_bar/filter_editor/lib/index.ts | 29 + .../filter_editor/phrase_suggestor.tsx | 7 +- .../filter_editor/phrase_value_input.tsx | 32 +- .../filter_editor/phrases_values_input.tsx | 49 +- .../filter_editor/range_value_input.tsx | 7 + .../filter_editor/value_input_type.tsx | 13 +- .../filter_bar/filter_item/filter_item.tsx | 2 +- .../filter_bar/filter_item/filter_items.tsx | 4 +- .../__snapshots__/filter_label.test.tsx.snap | 0 .../filter_label.test.tsx | 0 .../lib => filter_label}/filter_label.tsx | 4 +- .../public/filter_bar/index.tsx | 2 +- .../filters_builder/__mock__/filters.ts | 630 ++++++++++++++++++ .../__stories__/filter_builder.stories.tsx | 188 ++++++ .../public/filters_builder/assets/add.svg | 33 + .../public/filters_builder/assets/or.svg | 33 + .../filters_builder/filters_builder.tsx | 126 ++++ .../filters_builder_context.ts | 26 + .../filters_builder_filter_group.tsx | 149 +++++ .../filters_builder_filter_item.tsx | 317 +++++++++ ...ilters_builder_filter_item_field_input.tsx | 51 ++ ...ers_builder_filter_item_operator_input.tsx | 61 ++ ...ters_builder_filter_item_params_editor.tsx | 113 ++++ .../filters_builder_filter_item/index.ts | 9 + .../filters_builder_reducer.ts | 99 +++ .../filters_builder/filters_builder_types.ts | 10 + .../filters_builder_utils.test.ts | 261 ++++++++ .../filters_builder/filters_builder_utils.ts | 360 ++++++++++ .../public/filters_builder/index.ts | 22 + src/plugins/unified_search/public/index.ts | 6 +- src/plugins/unified_search/public/plugin.ts | 6 +- .../filter_editor_wrapper.tsx | 4 +- .../query_bar_menu_panels.tsx | 5 +- .../query_bar_top_row.test.tsx | 7 +- .../query_string_input/query_bar_top_row.tsx | 5 +- .../query_string_input.test.tsx | 9 +- .../query_string_input/query_string_input.tsx | 13 +- .../text_based_languages_editor/index.tsx | 4 +- .../saved_query_management_list.tsx | 5 +- .../public/search_bar/create_search_bar.tsx | 6 +- .../public/search_bar/search_bar.tsx | 4 +- src/plugins/unified_search/public/services.ts | 4 - src/plugins/unified_search/public/types.ts | 21 +- .../unified_search/public/utils/index.ts | 8 + .../unified_search/public/utils/or_filter.ts | 47 ++ .../guidance_panel/guidance_panel.tsx | 4 +- .../public/components/search_bar.test.tsx | 7 - .../graph/public/components/search_bar.tsx | 4 +- .../components/query_bar/index.test.tsx | 7 - .../actions_log_date_range_picker.tsx | 5 +- .../timeline/query_bar/index.test.tsx | 7 - .../legacy_uptime/pages/overview.test.tsx | 7 - .../components/query_bar/query_bar.test.tsx | 7 +- 62 files changed, 2791 insertions(+), 183 deletions(-) rename src/plugins/unified_search/public/filter_bar/filter_editor/{index.tsx => filter_editor.tsx} (90%) create mode 100644 src/plugins/unified_search/public/filter_bar/filter_editor/index.ts rename src/plugins/unified_search/public/{utils => filter_bar/filter_editor/lib}/helpers.test.ts (100%) rename src/plugins/unified_search/public/{utils => filter_bar/filter_editor/lib}/helpers.ts (92%) create mode 100644 src/plugins/unified_search/public/filter_bar/filter_editor/lib/index.ts rename src/plugins/unified_search/public/filter_bar/{filter_editor/lib => filter_label}/__snapshots__/filter_label.test.tsx.snap (100%) rename src/plugins/unified_search/public/filter_bar/{filter_editor/lib => filter_label}/filter_label.test.tsx (100%) rename src/plugins/unified_search/public/filter_bar/{filter_editor/lib => filter_label}/filter_label.tsx (94%) create mode 100644 src/plugins/unified_search/public/filters_builder/__mock__/filters.ts create mode 100644 src/plugins/unified_search/public/filters_builder/__stories__/filter_builder.stories.tsx create mode 100644 src/plugins/unified_search/public/filters_builder/assets/add.svg create mode 100644 src/plugins/unified_search/public/filters_builder/assets/or.svg create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder.tsx create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_context.ts create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_filter_group.tsx create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item.tsx create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_field_input.tsx create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_operator_input.tsx create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_params_editor.tsx create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/index.ts create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_reducer.ts create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_types.ts create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_utils.test.ts create mode 100644 src/plugins/unified_search/public/filters_builder/filters_builder_utils.ts create mode 100644 src/plugins/unified_search/public/filters_builder/index.ts create mode 100644 src/plugins/unified_search/public/utils/or_filter.ts diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index ed50d78f4de3c..c8eefbdd92c6e 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -291,7 +291,6 @@ export function plugin(initializerContext: PluginInitializerContext { - appName: string; - uiSettings: CoreStart['uiSettings']; - savedObjects: CoreStart['savedObjects']; - notifications: CoreStart['notifications']; - application: CoreStart['application']; - http: CoreStart['http']; - storage: IStorageWrapper; - data: DataPublicPluginStart; - usageCollection?: UsageCollectionStart; -} diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index 44c281560fd54..94c869bb54a1c 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -26,8 +26,8 @@ import { EuiToolTip, } from '@elastic/eui'; import type { DataViewListItem } from '@kbn/data-views-plugin/public'; -import { IDataPluginServices } from '@kbn/data-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { IUnifiedSearchPluginServices } from '../types'; import type { DataViewPickerPropsExtended } from '.'; import { DataViewsList } from './dataview_list'; import type { TextBasedLanguagesListProps } from './text_languages_list'; @@ -82,7 +82,7 @@ export function ChangeDataView({ const [isTextLangTransitionModalVisible, setIsTextLangTransitionModalVisible] = useState(false); const [selectedDataViewId, setSelectedDataViewId] = useState(currentDataViewId); - const kibana = useKibana(); + const kibana = useKibana(); const { application, data, storage } = kibana.services; const styles = changeDataViewStyles({ fullWidth: trigger.fullWidth }); const [isTextLangTransitionModalDismissed, setIsTextLangTransitionModalDismissed] = useState(() => diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.test.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.test.tsx index 87d0eca815cd8..cac21f9732904 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.test.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.test.tsx @@ -7,7 +7,8 @@ */ import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; -import { FilterEditor, Props } from '.'; +import type { FilterEditorProps } from '.'; +import { FilterEditor } from '.'; import React from 'react'; jest.mock('@kbn/kibana-react-plugin/public', () => { @@ -32,7 +33,7 @@ describe('', () => { let testBed: TestBed; beforeEach(async () => { - const defaultProps: Omit = { + const defaultProps: Omit = { filter: { meta: { type: 'phase', diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx similarity index 90% rename from src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx rename to src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx index 0bdda65b32c9d..cdf4af1746e5c 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx @@ -48,8 +48,9 @@ import { Operator } from './lib/filter_operators'; import { PhraseValueInput } from './phrase_value_input'; import { PhrasesValuesInput } from './phrases_values_input'; import { RangeValueInput } from './range_value_input'; +import { getFieldValidityAndErrorMessage } from './lib/helpers'; -export interface Props { +export interface FilterEditorProps { filter: Filter; indexPatterns: DataView[]; onSubmit: (filter: Filter) => void; @@ -84,8 +85,8 @@ const updateButtonLabel = i18n.translate('unifiedSearch.filter.filterEditor.upda defaultMessage: 'Update filter', }); -class FilterEditorUI extends Component { - constructor(props: Props) { +class FilterEditorUI extends Component { + constructor(props: FilterEditorProps) { super(props); this.state = { selectedIndexPattern: this.getIndexPatternFromFilter(), @@ -356,32 +357,55 @@ class FilterEditorUI extends Component { return ''; } + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage( + this.state.selectedField, + this.state.params + ); + switch (this.state.selectedOperator.type) { case 'exists': return ''; case 'phrase': return ( - + label={this.props.intl.formatMessage({ + id: 'unifiedSearch.filter.filterEditor.valueInputLabel', + defaultMessage: 'Value', + })} + isInvalid={isInvalid} + error={errorMessage} + > + + ); case 'phrases': return ( - + label={this.props.intl.formatMessage({ + id: 'unifiedSearch.filter.filterEditor.valuesSelectLabel', + defaultMessage: 'Values', + })} + > + + ); case 'range': return ( diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/index.ts b/src/plugins/unified_search/public/filter_bar/filter_editor/index.ts new file mode 100644 index 0000000000000..70b448a80dc33 --- /dev/null +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/index.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { Operator } from './lib'; + +export { + getFieldFromFilter, + getOperatorFromFilter, + getFilterableFields, + getOperatorOptions, + validateParams, + isFilterValid, + isOperator, + isNotOperator, + isOneOfOperator, + isNotOneOfOperator, + isBetweenOperator, + isNotBetweenOperator, + existsOperator, + doesNotExistOperator, + FILTER_OPERATORS, +} from './lib'; + +export type { GenericComboBoxProps } from './generic_combo_box'; +export type { PhraseSuggestorProps } from './phrase_suggestor'; +export type { PhrasesSuggestorProps } from './phrases_values_input'; + +export { GenericComboBox } from './generic_combo_box'; +export { PhraseSuggestor } from './phrase_suggestor'; +export { PhrasesValuesInput } from './phrases_values_input'; +export { PhraseValueInput } from './phrase_value_input'; +export { RangeValueInput, isRangeParams } from './range_value_input'; +export { ValueInputType } from './value_input_type'; + +export { FilterEditor } from './filter_editor'; +export type { FilterEditorProps } from './filter_editor'; diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.ts b/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.ts index 0863d10fe0c10..b59ddcc424ff8 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.ts +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.ts @@ -37,7 +37,7 @@ export function getOperatorOptions(field: DataViewField) { } export function validateParams(params: any, field: DataViewField) { - switch (field.type) { + switch (field?.type) { case 'date': const moment = typeof params === 'string' ? dateMath.parse(params) : null; return Boolean(typeof params === 'string' && moment && moment.isValid()); diff --git a/src/plugins/unified_search/public/utils/helpers.test.ts b/src/plugins/unified_search/public/filter_bar/filter_editor/lib/helpers.test.ts similarity index 100% rename from src/plugins/unified_search/public/utils/helpers.test.ts rename to src/plugins/unified_search/public/filter_bar/filter_editor/lib/helpers.test.ts diff --git a/src/plugins/unified_search/public/utils/helpers.ts b/src/plugins/unified_search/public/filter_bar/filter_editor/lib/helpers.ts similarity index 92% rename from src/plugins/unified_search/public/utils/helpers.ts rename to src/plugins/unified_search/public/filter_bar/filter_editor/lib/helpers.ts index 6f0a605fa0e14..c0246168671f0 100644 --- a/src/plugins/unified_search/public/utils/helpers.ts +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/lib/helpers.ts @@ -10,13 +10,13 @@ import type { DataViewField } from '@kbn/data-views-plugin/common'; import { i18n } from '@kbn/i18n'; import { KBN_FIELD_TYPES } from '@kbn/data-plugin/public'; import { isEmpty } from 'lodash'; -import { validateParams } from '../filter_bar/filter_editor/lib/filter_editor_utils'; +import { validateParams } from './filter_editor_utils'; export const getFieldValidityAndErrorMessage = ( field: DataViewField, value?: string | undefined ): { isInvalid: boolean; errorMessage?: string } => { - const type = field.type; + const type = field?.type; switch (type) { case KBN_FIELD_TYPES.DATE: case KBN_FIELD_TYPES.DATE_RANGE: diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/lib/index.ts b/src/plugins/unified_search/public/filter_bar/filter_editor/lib/index.ts new file mode 100644 index 0000000000000..8ae6e6bd25607 --- /dev/null +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/lib/index.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { getFieldValidityAndErrorMessage } from './helpers'; +export type { Operator } from './filter_operators'; +export { + isOperator, + isNotOperator, + isOneOfOperator, + isNotOneOfOperator, + isBetweenOperator, + isNotBetweenOperator, + existsOperator, + doesNotExistOperator, + FILTER_OPERATORS, +} from './filter_operators'; +export { + getFieldFromFilter, + getOperatorFromFilter, + getFilterableFields, + getOperatorOptions, + validateParams, + isFilterValid, +} from './filter_editor_utils'; diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx index dc987421e2661..ac5af32203c91 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx @@ -10,13 +10,12 @@ import React from 'react'; import { withKibana, KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; -import { IDataPluginServices } from '@kbn/data-plugin/public'; import { debounce } from 'lodash'; -import { getAutocomplete } from '../../services'; +import { IUnifiedSearchPluginServices } from '../../types'; export interface PhraseSuggestorProps { - kibana: KibanaReactContextValue; + kibana: KibanaReactContextValue; indexPattern: DataView; field: DataViewField; timeRangeForSuggestionsOverride?: boolean; @@ -80,7 +79,7 @@ export class PhraseSuggestorUI extends React.Com return; } this.setState({ isLoading: true }); - const suggestions = await getAutocomplete().getValueSuggestions({ + const suggestions = await this.services.unifiedSearch.autocomplete.getValueSuggestions({ indexPattern, field, query, diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx index c0fe3dc497025..210b201ec4c68 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/phrase_value_input.tsx @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { EuiFormRow } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n-react'; import { uniq } from 'lodash'; import React from 'react'; @@ -14,36 +13,27 @@ import { withKibana } from '@kbn/kibana-react-plugin/public'; import { GenericComboBox, GenericComboBoxProps } from './generic_combo_box'; import { PhraseSuggestorUI, PhraseSuggestorProps } from './phrase_suggestor'; import { ValueInputType } from './value_input_type'; -import { getFieldValidityAndErrorMessage } from '../../utils/helpers'; -interface Props extends PhraseSuggestorProps { +interface PhraseValueInputProps extends PhraseSuggestorProps { value?: string; onChange: (value: string | number | boolean) => void; intl: InjectedIntl; fullWidth?: boolean; + compressed?: boolean; + disabled?: boolean; + isInvalid?: boolean; } -class PhraseValueInputUI extends PhraseSuggestorUI { +class PhraseValueInputUI extends PhraseSuggestorUI { public render() { - const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage( - this.props.field, - this.props.value - ); - return ( - + <> {this.isSuggestingValues() ? ( this.renderWithSuggestions() ) : ( { value={this.props.value} onChange={this.props.onChange} field={this.props.field} - isInvalid={isInvalid} + isInvalid={this.props.isInvalid} /> )} - + ); } @@ -67,7 +57,9 @@ class PhraseValueInputUI extends PhraseSuggestorUI { const options = value ? uniq([valueAsStr, ...suggestions]) : suggestions; return ( void; onParamsUpdate: (value: string) => void; intl: InjectedIntl; fullWidth?: boolean; + compressed?: boolean; } -class PhrasesValuesInputUI extends PhraseSuggestorUI { +class PhrasesValuesInputUI extends PhraseSuggestorUI { public render() { const { suggestions } = this.state; - const { values, intl, onChange, fullWidth, onParamsUpdate } = this.props; + const { values, intl, onChange, fullWidth, onParamsUpdate, compressed } = this.props; const options = values ? uniq([...values, ...suggestions]) : suggestions; return ( - - option} - selectedOptions={values || []} - onSearchChange={this.onSearchChange} - onCreateOption={(option: string) => { - onParamsUpdate(option.trim()); - }} - onChange={onChange} - isClearable={false} - data-test-subj="filterParamsComboBox phrasesParamsComboxBox" - /> - + delimiter="," + options={options} + getLabel={(option) => option} + selectedOptions={values || []} + onSearchChange={this.onSearchChange} + onCreateOption={(option: string) => { + onParamsUpdate(option.trim()); + }} + onChange={onChange} + isClearable={false} + data-test-subj="filterParamsComboBox phrasesParamsComboxBox" + /> ); } } diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/range_value_input.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/range_value_input.tsx index 26a25886ac866..27a1d9db7739d 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/range_value_input.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/range_value_input.tsx @@ -28,6 +28,11 @@ interface Props { onChange: (params: RangeParamsPartial) => void; intl: InjectedIntl; fullWidth?: boolean; + compressed?: boolean; +} + +export function isRangeParams(params: any): params is RangeParams { + return Boolean(params && 'from' in params && 'to' in params); } function RangeValueInputUI(props: Props) { @@ -68,6 +73,7 @@ function RangeValueInputUI(props: Props) { startControl={ { @@ -37,12 +39,14 @@ class ValueInputTypeUI extends Component { public render() { const value = this.props.value; - const type = this.props.field.type; + const type = this.props.field?.type ?? 'string'; let inputElement: React.ReactNode; switch (type) { case 'string': inputElement = ( { case 'number_range': inputElement = ( { case 'date_range': inputElement = ( { inputElement = ( ); break; @@ -119,6 +129,7 @@ class ValueInputTypeUI extends Component { onChange={this.onBoolChange} className={this.props.className} fullWidth={this.props.fullWidth} + compressed={this.props.compressed} /> ); break; diff --git a/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx b/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx index abacffe4a46a9..3692b19f7e440 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx @@ -27,7 +27,7 @@ import { getDisplayValueFromFilter, getFieldDisplayValueFromFilter, } from '@kbn/data-plugin/public'; -import { FilterEditor } from '../filter_editor'; +import { FilterEditor } from '../filter_editor/filter_editor'; import { FilterView } from '../filter_view'; import { getIndexPatterns } from '../../services'; import { FilterPanelOption } from '../../types'; diff --git a/src/plugins/unified_search/public/filter_bar/filter_item/filter_items.tsx b/src/plugins/unified_search/public/filter_bar/filter_item/filter_items.tsx index 4c29c4284860d..c46a4973e2457 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_item/filter_items.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_item/filter_items.tsx @@ -11,11 +11,11 @@ import { css } from '@emotion/react'; import { EuiFlexItem } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n-react'; import type { Filter } from '@kbn/es-query'; -import { IDataPluginServices } from '@kbn/data-plugin/public'; import { METRIC_TYPE } from '@kbn/analytics'; import { DataView } from '@kbn/data-views-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { FilterItem, FilterItemProps } from './filter_item'; +import type { IUnifiedSearchPluginServices } from '../../types'; /** * Properties for the filter items component, which will render a single filter pill for every filter that is sent in @@ -41,7 +41,7 @@ export interface FilterItemsProps { const FilterItemsUI = React.memo(function FilterItemsUI(props: FilterItemsProps) { const groupRef = useRef(null); - const kibana = useKibana(); + const kibana = useKibana(); const { appName, usageCollection, uiSettings } = kibana.services; const { readOnly = false } = props; diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/lib/__snapshots__/filter_label.test.tsx.snap b/src/plugins/unified_search/public/filter_bar/filter_label/__snapshots__/filter_label.test.tsx.snap similarity index 100% rename from src/plugins/unified_search/public/filter_bar/filter_editor/lib/__snapshots__/filter_label.test.tsx.snap rename to src/plugins/unified_search/public/filter_bar/filter_label/__snapshots__/filter_label.test.tsx.snap diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.test.tsx b/src/plugins/unified_search/public/filter_bar/filter_label/filter_label.test.tsx similarity index 100% rename from src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.test.tsx rename to src/plugins/unified_search/public/filter_bar/filter_label/filter_label.test.tsx diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx b/src/plugins/unified_search/public/filter_bar/filter_label/filter_label.tsx similarity index 94% rename from src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx rename to src/plugins/unified_search/public/filter_bar/filter_label/filter_label.tsx index 35c05316465f8..261a2a6e7afb2 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_label/filter_label.tsx @@ -10,8 +10,8 @@ import React, { Fragment } from 'react'; import { EuiTextColor } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Filter, FILTERS } from '@kbn/es-query'; -import { existsOperator, isOneOfOperator } from './filter_operators'; -import type { FilterLabelStatus } from '../../filter_item/filter_item'; +import type { FilterLabelStatus } from '../filter_item/filter_item'; +import { existsOperator, isOneOfOperator } from '../filter_editor'; export interface FilterLabelProps { filter: Filter; diff --git a/src/plugins/unified_search/public/filter_bar/index.tsx b/src/plugins/unified_search/public/filter_bar/index.tsx index a70b6b93de5dd..a0fee65518fa8 100644 --- a/src/plugins/unified_search/public/filter_bar/index.tsx +++ b/src/plugins/unified_search/public/filter_bar/index.tsx @@ -29,7 +29,7 @@ export const FilterItems = (props: React.ComponentProps) ); -const LazyFilterLabel = React.lazy(() => import('./filter_editor/lib/filter_label')); +const LazyFilterLabel = React.lazy(() => import('./filter_label/filter_label')); /** * Renders the label for a single filter pill */ diff --git a/src/plugins/unified_search/public/filters_builder/__mock__/filters.ts b/src/plugins/unified_search/public/filters_builder/__mock__/filters.ts new file mode 100644 index 0000000000000..03d5b4333cff4 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/__mock__/filters.ts @@ -0,0 +1,630 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Filter } from '@kbn/es-query'; + +export const getFiltersMock = () => + [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 1", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 1", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + type: 'OR', + params: [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 2", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 2", + }, + }, + $state: { + store: 'appState', + }, + }, + [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 3", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 3", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 4", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 4", + }, + }, + $state: { + store: 'appState', + }, + }, + ], + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 5", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 5", + }, + }, + $state: { + store: 'appState', + }, + }, + ], + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 6", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 6", + }, + }, + $state: { + store: 'appState', + }, + }, + ] as Filter[]; + +export const getFiltersMockOrHide = () => + [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 1", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 1", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 2", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 2", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 3", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 3", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 4", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 4", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 5", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 5", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 6", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 6", + }, + }, + $state: { + store: 'appState', + }, + }, + ] as Filter[]; + +export const getDataThatNeedsNormalized = () => + [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 1", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 1", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + type: 'OR', + params: [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 2", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 2", + }, + }, + $state: { + store: 'appState', + }, + }, + [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 3", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 3", + }, + }, + $state: { + store: 'appState', + }, + }, + ], + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 5", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 5", + }, + }, + $state: { + store: 'appState', + }, + }, + ], + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 6", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 6", + }, + }, + $state: { + store: 'appState', + }, + }, + ] as Filter[]; + +export const getDataAfterNormalized = () => + [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 1", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 1", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + type: 'OR', + params: [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 2", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 2", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 3", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 3", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 5", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 5", + }, + }, + $state: { + store: 'appState', + }, + }, + ], + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 6", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 6", + }, + }, + $state: { + store: 'appState', + }, + }, + ] as Filter[]; + +export const getDataThatNeedNotNormalized = () => + [ + { + meta: { + type: 'OR', + params: [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 2", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 2", + }, + }, + $state: { + store: 'appState', + }, + }, + [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 3", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 3", + }, + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 4", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 4", + }, + }, + $state: { + store: 'appState', + }, + }, + ], + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 5", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 5", + }, + }, + $state: { + store: 'appState', + }, + }, + ], + }, + }, + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories 6", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories 6", + }, + }, + $state: { + store: 'appState', + }, + }, + ] as Filter[]; diff --git a/src/plugins/unified_search/public/filters_builder/__stories__/filter_builder.stories.tsx b/src/plugins/unified_search/public/filters_builder/__stories__/filter_builder.stories.tsx new file mode 100644 index 0000000000000..aebf85440dfe4 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/__stories__/filter_builder.stories.tsx @@ -0,0 +1,188 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC } from 'react'; +import { ComponentStory } from '@storybook/react'; +import { I18nProvider } from '@kbn/i18n-react'; +import { EuiForm } from '@elastic/eui'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { action } from '@storybook/addon-actions'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import type { Filter } from '@kbn/es-query'; +import { getFiltersMock, getFiltersMockOrHide } from '../__mock__/filters'; +import FiltersBuilder, { FiltersBuilderProps } from '../filters_builder'; + +export default { + title: 'Filters Builder', + component: FiltersBuilder, + decorators: [(story: Function) => {story()}], +}; + +const Template: ComponentStory> = (args) => ; + +export const Default = Template.bind({}); + +Default.decorators = [ + (Story) => ( + + + + + + ), +]; + +const mockedDataView = { + id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + title: 'logstash-*', + fields: [ + { + name: 'category.keyword', + type: 'string', + esTypes: ['integer'], + aggregatable: true, + filterable: true, + searchable: true, + }, + ], +} as DataView; + +const filters = getFiltersMock(); + +Default.args = { + filters, + dataView: mockedDataView, + onChange: (f: Filter[]) => {}, + hideOr: false, +}; + +export const withoutOR = Template.bind({}); +withoutOR.args = { ...Default.args, filters: getFiltersMockOrHide(), hideOr: true }; + +withoutOR.decorators = [ + (Story) => ( + + + + + + ), +]; + +const createMockWebStorage = () => ({ + clear: action('clear'), + getItem: action('getItem'), + key: action('key'), + removeItem: action('removeItem'), + setItem: action('setItem'), + length: 0, +}); + +const createMockStorage = () => ({ + storage: createMockWebStorage(), + set: action('set'), + remove: action('remove'), + clear: action('clear'), + get: () => true, +}); + +const services = { + uiSettings: { + get: () => true, + }, + savedObjects: action('savedObjects'), + notifications: action('notifications'), + http: { + basePath: { + prepend: () => 'http://test', + }, + }, + docLinks: { + links: { + query: { + kueryQuerySyntax: '', + }, + }, + }, + storage: createMockStorage(), + data: { + query: { + savedQueries: { + findSavedQueries: () => + Promise.resolve({ + queries: [ + { + id: 'testwewe', + attributes: { + title: 'Saved query 1', + description: '', + query: { + query: 'category.keyword : "Men\'s Shoes" ', + language: 'kuery', + }, + filters: [], + }, + }, + { + id: '0173d0d0-b19a-11ec-8323-837d6b231b82', + attributes: { + title: 'test', + description: '', + query: { + query: '', + language: 'kuery', + }, + filters: [ + { + meta: { + index: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Accessories", + }, + }, + query: { + match_phrase: { + 'category.keyword': "Men's Accessories", + }, + }, + $state: { + store: 'appState', + }, + }, + ], + }, + }, + ], + }), + }, + }, + dataViews: { + getIdsWithTitle: () => [ + { id: '8a0b7cd0-b0c4-11ec-92b2-73d62e0d28a9', title: 'logstash-*' }, + { id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', title: 'test-*' }, + ], + }, + }, + unifiedSearch: { + autocomplete: { + hasQuerySuggestions: () => Promise.resolve(false), + getQuerySuggestions: () => [], + getValueSuggestions: () => + new Promise((resolve) => { + setTimeout(() => { + resolve([]); + }, 300); + }), + }, + }, +}; diff --git a/src/plugins/unified_search/public/filters_builder/assets/add.svg b/src/plugins/unified_search/public/filters_builder/assets/add.svg new file mode 100644 index 0000000000000..cfc9907424f62 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/assets/add.svg @@ -0,0 +1,33 @@ + + + add + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/unified_search/public/filters_builder/assets/or.svg b/src/plugins/unified_search/public/filters_builder/assets/or.svg new file mode 100644 index 0000000000000..d0be3ff2e77fe --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/assets/or.svg @@ -0,0 +1,33 @@ + + + or + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder.tsx b/src/plugins/unified_search/public/filters_builder/filters_builder.tsx new file mode 100644 index 0000000000000..c7251bb78518c --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder.tsx @@ -0,0 +1,126 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useEffect, useReducer, useCallback, useState, useMemo } from 'react'; +import { EuiDragDropContext, DragDropContextProps, useEuiPaddingSize } from '@elastic/eui'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import type { Filter } from '@kbn/es-query'; +import { css } from '@emotion/css'; +import { FiltersBuilderContextType } from './filters_builder_context'; +import { ConditionTypes } from '../utils'; +import { FilterGroup } from './filters_builder_filter_group'; +import { FiltersBuilderReducer } from './filters_builder_reducer'; + +export interface FiltersBuilderProps { + filters: Filter[]; + dataView: DataView; + onChange: (filters: Filter[]) => void; + timeRangeForSuggestionsOverride?: boolean; + maxDepth?: number; + hideOr?: boolean; +} + +const rootLevelConditionType = ConditionTypes.AND; +const DEFAULT_MAX_DEPTH = 10; + +function FiltersBuilder({ + onChange, + dataView, + filters, + timeRangeForSuggestionsOverride, + maxDepth = DEFAULT_MAX_DEPTH, + hideOr = false, +}: FiltersBuilderProps) { + const [state, dispatch] = useReducer(FiltersBuilderReducer, { filters }); + const [dropTarget, setDropTarget] = useState(''); + const mPaddingSize = useEuiPaddingSize('m'); + + const filtersBuilderStyles = useMemo( + () => css` + .filter-builder__panel { + &.filter-builder__panel-nested { + padding: ${mPaddingSize} 0; + } + } + + .filter-builder__item { + &.filter-builder__item-nested { + padding: 0 ${mPaddingSize}; + } + } + `, + [mPaddingSize] + ); + + useEffect(() => { + if (state.filters !== filters) { + onChange(state.filters); + } + }, [filters, onChange, state.filters]); + + const handleMoveFilter = useCallback( + (pathFrom: string, pathTo: string, conditionalType: ConditionTypes) => { + if (pathFrom === pathTo) { + return null; + } + + dispatch({ + type: 'moveFilter', + payload: { + pathFrom, + pathTo, + conditionalType, + }, + }); + }, + [] + ); + + const onDragEnd: DragDropContextProps['onDragEnd'] = ({ combine, source, destination }) => { + if (source && destination) { + handleMoveFilter(source.droppableId, destination.droppableId, ConditionTypes.AND); + } + + if (source && combine) { + handleMoveFilter(source.droppableId, combine.droppableId, ConditionTypes.OR); + } + setDropTarget(''); + }; + + const onDragActive: DragDropContextProps['onDragUpdate'] = ({ destination, combine }) => { + if (destination) { + setDropTarget(destination.droppableId); + } + + if (combine) { + setDropTarget(combine.droppableId); + } + }; + + return ( +
+ + + + + +
+ ); +} + +// React.lazy support +// eslint-disable-next-line import/no-default-export +export default FiltersBuilder; diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_context.ts b/src/plugins/unified_search/public/filters_builder/filters_builder_context.ts new file mode 100644 index 0000000000000..8dfab23f97887 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_context.ts @@ -0,0 +1,26 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { Dispatch } from 'react'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import type { FiltersBuilderActions } from './filters_builder_reducer'; + +interface FiltersBuilderContextType { + dataView: DataView; + dispatch: Dispatch; + globalParams: { + maxDepth: number; + hideOr: boolean; + }; + dropTarget: string; + timeRangeForSuggestionsOverride?: boolean; +} + +export const FiltersBuilderContextType = React.createContext( + {} as FiltersBuilderContextType +); diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_filter_group.tsx b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_group.tsx new file mode 100644 index 0000000000000..adb77f7b9d2ea --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_group.tsx @@ -0,0 +1,149 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useContext, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiPanel, + EuiText, + useEuiBackgroundColor, + useEuiPaddingSize, +} from '@elastic/eui'; +import { Filter } from '@kbn/es-query'; +import { css, cx } from '@emotion/css'; +import type { Path } from './filters_builder_types'; +import { ConditionTypes, getConditionalOperationType } from '../utils'; +import { FilterItem } from './filters_builder_filter_item'; +import { FiltersBuilderContextType } from './filters_builder_context'; +import { getPathInArray } from './filters_builder_utils'; + +export interface FilterGroupProps { + filters: Filter[]; + conditionType: ConditionTypes; + path: Path; + + /** @internal used for recursive rendering **/ + renderedLevel?: number; + reverseBackground?: boolean; +} + +/** @internal **/ +const Delimiter = ({ + color, + conditionType, +}: { + color: 'subdued' | 'plain'; + conditionType: ConditionTypes; +}) => { + const xsPadding = useEuiPaddingSize('xs'); + const mPadding = useEuiPaddingSize('m'); + const backgroundColor = useEuiBackgroundColor(color); + + const delimiterStyles = useMemo( + () => css` + position: relative; + + .filter-builder__delimiter_text { + position: absolute; + display: block; + padding: ${xsPadding}; + top: 0; + left: ${mPadding}; + background: ${backgroundColor}; + } + `, + [backgroundColor, mPadding, xsPadding] + ); + + return ( +
+ + + {i18n.translate('unifiedSearch.filter.filtersBuilder.delimiterLabel', { + defaultMessage: '{conditionType}', + values: { + conditionType, + }, + })} + +
+ ); +}; + +export const FilterGroup = ({ + filters, + conditionType, + path, + reverseBackground = false, + renderedLevel = 0, +}: FilterGroupProps) => { + const { + globalParams: { maxDepth, hideOr }, + } = useContext(FiltersBuilderContextType); + + const pathInArray = getPathInArray(path); + const isDepthReached = maxDepth <= pathInArray.length; + const orDisabled = hideOr || (isDepthReached && conditionType === ConditionTypes.AND); + const andDisabled = isDepthReached && conditionType === ConditionTypes.OR; + const removeDisabled = pathInArray.length <= 1 && filters.length === 1; + const shouldNormalizeFirstLevel = + !path && filters.length === 1 && getConditionalOperationType(filters[0]); + + if (shouldNormalizeFirstLevel) { + reverseBackground = true; + renderedLevel -= 1; + } + + const color = reverseBackground ? 'plain' : 'subdued'; + + const renderedFilters = filters.map((filter, index, acc) => ( + + + + + + {conditionType && index + 1 < acc.length ? ( + + {conditionType === ConditionTypes.OR && ( + + )} + + ) : null} + + )); + + return shouldNormalizeFirstLevel ? ( + <>{renderedFilters} + ) : ( + 0, + })} + > + {renderedFilters} + + ); +}; diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item.tsx b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item.tsx new file mode 100644 index 0000000000000..30b73d397b674 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item.tsx @@ -0,0 +1,317 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback, useContext, useMemo } from 'react'; +import { + EuiButtonIcon, + EuiDraggable, + EuiDroppable, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIcon, + EuiPanel, + useEuiTheme, +} from '@elastic/eui'; +import { buildEmptyFilter, FieldFilter, Filter, getFilterParams } from '@kbn/es-query'; +import { DataViewField } from '@kbn/data-views-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { cx, css } from '@emotion/css'; + +import add from '../assets/add.svg'; +import or from '../assets/or.svg'; + +import { FieldInput } from './filters_builder_filter_item_field_input'; +import { OperatorInput } from './filters_builder_filter_item_operator_input'; +import { ParamsEditor } from './filters_builder_filter_item_params_editor'; +import { ConditionTypes, getConditionalOperationType } from '../../utils'; +import { FiltersBuilderContextType } from '../filters_builder_context'; +import { FilterGroup } from '../filters_builder_filter_group'; +import type { Path } from '../filters_builder_types'; +import { getFieldFromFilter, getOperatorFromFilter } from '../../filter_bar/filter_editor'; +import { Operator } from '../../filter_bar/filter_editor'; + +export interface FilterItemProps { + path: Path; + filter: Filter; + disableOr: boolean; + disableAnd: boolean; + disableRemove: boolean; + color: 'plain' | 'subdued'; + index: number; + + /** @internal used for recursive rendering **/ + renderedLevel: number; + reverseBackground: boolean; +} + +const cursorAddStyles = css` + cursor: url(${add}), auto; +`; + +const cursorOrStyles = css` + cursor: url(${or}), auto; +`; + +export function FilterItem({ + filter, + path, + reverseBackground, + disableOr, + disableAnd, + disableRemove, + color, + index, + renderedLevel, +}: FilterItemProps) { + const { + dispatch, + dataView, + dropTarget, + globalParams: { hideOr }, + timeRangeForSuggestionsOverride, + } = useContext(FiltersBuilderContextType); + const conditionalOperationType = getConditionalOperationType(filter); + const { euiTheme } = useEuiTheme(); + + const grabIconStyles = useMemo( + () => css` + margin: 0 ${euiTheme.size.xxs}; + `, + [euiTheme.size.xxs] + ); + + let field: DataViewField | undefined; + let operator: Operator | undefined; + let params: Filter['meta']['params'] | undefined; + + if (!conditionalOperationType) { + field = getFieldFromFilter(filter as FieldFilter, dataView); + operator = getOperatorFromFilter(filter); + params = getFilterParams(filter); + } + + const onHandleField = useCallback( + (selectedField: DataViewField) => { + dispatch({ + type: 'updateFilter', + payload: { path, field: selectedField }, + }); + }, + [dispatch, path] + ); + + const onHandleOperator = useCallback( + (selectedOperator: Operator) => { + dispatch({ + type: 'updateFilter', + payload: { path, field, operator: selectedOperator }, + }); + }, + [dispatch, path, field] + ); + + const onHandleParamsChange = useCallback( + (selectedParams: string) => { + dispatch({ + type: 'updateFilter', + payload: { path, field, operator, params: selectedParams }, + }); + }, + [dispatch, path, field, operator] + ); + + const onHandleParamsUpdate = useCallback( + (value: Filter['meta']['params']) => { + dispatch({ + type: 'updateFilter', + payload: { path, params: [value, ...(params || [])] }, + }); + }, + [dispatch, path, params] + ); + + const onRemoveFilter = useCallback(() => { + dispatch({ + type: 'removeFilter', + payload: { + path, + }, + }); + }, [dispatch, path]); + + const onAddFilter = useCallback( + (conditionalType: ConditionTypes) => { + dispatch({ + type: 'addFilter', + payload: { + path, + filter: buildEmptyFilter(false, dataView.id), + conditionalType, + }, + }); + }, + [dispatch, dataView.id, path] + ); + + const onAddButtonClick = useCallback(() => onAddFilter(ConditionTypes.AND), [onAddFilter]); + const onOrButtonClick = useCallback(() => onAddFilter(ConditionTypes.OR), [onAddFilter]); + + if (!dataView) { + return null; + } + + return ( +
0, + })} + > + {conditionalOperationType ? ( + + ) : ( + + + {(provided) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {!hideOr ? ( + + + + ) : null} + + + + + + + + + + )} + + + )} +
+ ); +} diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_field_input.tsx b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_field_input.tsx new file mode 100644 index 0000000000000..3ff823a09cb5d --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_field_input.tsx @@ -0,0 +1,51 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; +import { useGeneratedHtmlId } from '@elastic/eui'; +import { getFilterableFields, GenericComboBox } from '../../filter_bar/filter_editor'; + +interface FieldInputProps { + dataView: DataView; + onHandleField: (field: DataViewField) => void; + field?: DataViewField; +} + +export function FieldInput({ field, dataView, onHandleField }: FieldInputProps) { + const fields = dataView ? getFilterableFields(dataView) : []; + const id = useGeneratedHtmlId({ prefix: 'fieldInput' }); + + const onFieldChange = useCallback( + ([selectedField]: DataViewField[]) => { + onHandleField(selectedField); + }, + [onHandleField] + ); + + const getLabel = useCallback((view: DataViewField) => view.customLabel || view.name, []); + + return ( + + ); +} diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_operator_input.tsx b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_operator_input.tsx new file mode 100644 index 0000000000000..2f73df3596212 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_operator_input.tsx @@ -0,0 +1,61 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import type { DataViewField } from '@kbn/data-views-plugin/common'; +import type { Operator } from '../../filter_bar/filter_editor'; +import { getOperatorOptions, GenericComboBox } from '../../filter_bar/filter_editor'; + +interface OperatorInputProps { + field: DataViewField | undefined; + operator: Operator | undefined; + params: TParams; + onHandleOperator: (operator: Operator, params?: TParams) => void; +} + +export function OperatorInput({ + field, + operator, + params, + onHandleOperator, +}: OperatorInputProps) { + const operators = field ? getOperatorOptions(field) : []; + + const onOperatorChange = useCallback( + ([selectedOperator]: Operator[]) => { + const selectedParams = selectedOperator === operator ? params : undefined; + + onHandleOperator(selectedOperator, selectedParams); + }, + [onHandleOperator, operator, params] + ); + + return ( + message} + onChange={onOperatorChange} + singleSelection={{ asPlainText: true }} + isClearable={false} + /> + ); +} diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_params_editor.tsx b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_params_editor.tsx new file mode 100644 index 0000000000000..17c571ac7ed39 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/filters_builder_filter_item_params_editor.tsx @@ -0,0 +1,113 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback } from 'react'; +import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; +import { EuiFormRow } from '@elastic/eui'; +import type { Operator } from '../../filter_bar/filter_editor'; +import { + PhraseValueInput, + PhrasesValuesInput, + RangeValueInput, + isRangeParams, +} from '../../filter_bar/filter_editor'; +import { getFieldValidityAndErrorMessage } from '../../filter_bar/filter_editor/lib'; + +interface ParamsEditorProps { + dataView: DataView; + params: TParams; + onHandleParamsChange: (params: TParams) => void; + onHandleParamsUpdate: (value: TParams) => void; + timeRangeForSuggestionsOverride?: boolean; + field?: DataViewField; + operator?: Operator; +} + +export function ParamsEditor({ + dataView, + field, + operator, + params, + onHandleParamsChange, + onHandleParamsUpdate, + timeRangeForSuggestionsOverride, +}: ParamsEditorProps) { + const onParamsChange = useCallback( + (selectedParams) => { + onHandleParamsChange(selectedParams); + }, + [onHandleParamsChange] + ); + + const onParamsUpdate = useCallback( + (value) => { + onHandleParamsUpdate(value); + }, + [onHandleParamsUpdate] + ); + + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage( + field!, + typeof params === 'string' ? params : undefined + ); + + switch (operator?.type) { + case 'exists': + return null; + case 'phrase': + return ( + + + + ); + case 'phrases': + return ( + + ); + case 'range': + return ( + + ); + default: + return ( + + ); + } +} diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/index.ts b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/index.ts new file mode 100644 index 0000000000000..07dd57964a13e --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_filter_item/index.ts @@ -0,0 +1,9 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { FilterItem } from './filters_builder_filter_item'; diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_reducer.ts b/src/plugins/unified_search/public/filters_builder/filters_builder_reducer.ts new file mode 100644 index 0000000000000..3dde3bdddac67 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_reducer.ts @@ -0,0 +1,99 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Reducer } from 'react'; +import type { Filter } from '@kbn/es-query'; +import type { DataViewField } from '@kbn/data-views-plugin/common'; +import type { Path } from './filters_builder_types'; +import type { ConditionTypes } from '../utils'; +import { addFilter, moveFilter, removeFilter, updateFilter } from './filters_builder_utils'; +import type { Operator } from '../filter_bar/filter_editor'; + +/** @internal **/ +export interface FiltersBuilderState { + filters: Filter[]; +} + +/** @internal **/ +export interface AddFilterPayload { + path: Path; + filter: Filter; + conditionalType: ConditionTypes; +} + +/** @internal **/ +export interface UpdateFilterPayload { + path: string; + field?: DataViewField; + operator?: Operator; + params?: Filter['meta']['params']; +} + +/** @internal **/ +export interface RemoveFilterPayload { + path: Path; +} + +/** @internal **/ +export interface MoveFilterPayload { + pathFrom: Path; + pathTo: Path; + conditionalType: ConditionTypes; +} + +/** @internal **/ +export type FiltersBuilderActions = + | { type: 'addFilter'; payload: AddFilterPayload } + | { type: 'removeFilter'; payload: RemoveFilterPayload } + | { type: 'moveFilter'; payload: MoveFilterPayload } + | { type: 'updateFilter'; payload: UpdateFilterPayload }; + +export const FiltersBuilderReducer: Reducer = ( + state, + action +) => { + switch (action.type) { + case 'addFilter': + return { + filters: addFilter( + state.filters, + action.payload.filter, + action.payload.path, + action.payload.conditionalType + ), + }; + case 'removeFilter': + return { + ...state, + filters: removeFilter(state.filters, action.payload.path), + }; + case 'moveFilter': + return { + ...state, + filters: moveFilter( + state.filters, + action.payload.pathFrom, + action.payload.pathTo, + action.payload.conditionalType + ), + }; + case 'updateFilter': + return { + ...state, + filters: updateFilter( + state.filters, + action.payload.path, + action.payload.field, + action.payload.operator, + action.payload.params + ), + }; + default: + throw new Error('wrong action'); + } +}; diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_types.ts b/src/plugins/unified_search/public/filters_builder/filters_builder_types.ts new file mode 100644 index 0000000000000..24d0b9015aa74 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_types.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** @internal **/ +export type Path = string; diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_utils.test.ts b/src/plugins/unified_search/public/filters_builder/filters_builder_utils.test.ts new file mode 100644 index 0000000000000..517a0cea4cce7 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_utils.test.ts @@ -0,0 +1,261 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { buildEmptyFilter, Filter } from '@kbn/es-query'; +import { ConditionTypes } from '../utils'; +import { + getFilterByPath, + getPathInArray, + addFilter, + removeFilter, + moveFilter, + normalizeFilters, +} from './filters_builder_utils'; +import type { FilterItem } from '../utils'; +import { getConditionalOperationType } from '../utils'; + +import { + getDataAfterNormalized, + getDataThatNeedNotNormalized, + getDataThatNeedsNormalized, + getFiltersMock, +} from './__mock__/filters'; + +describe('filters_builder_utils', () => { + let filters: Filter[]; + beforeAll(() => { + filters = getFiltersMock(); + }); + + describe('getFilterByPath', () => { + test('should return correct filterByPath', () => { + expect(getFilterByPath(filters, '0')).toMatchInlineSnapshot(` + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "key": "category.keyword", + "negate": false, + "params": Object { + "query": "Men's Accessories 1", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "category.keyword": "Men's Accessories 1", + }, + }, + } + `); + expect(getFilterByPath(filters, '2')).toMatchInlineSnapshot(` + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "key": "category.keyword", + "negate": false, + "params": Object { + "query": "Men's Accessories 6", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "category.keyword": "Men's Accessories 6", + }, + }, + } + `); + expect(getFilterByPath(filters, '1.2')).toMatchInlineSnapshot(` + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "key": "category.keyword", + "negate": false, + "params": Object { + "query": "Men's Accessories 5", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "category.keyword": "Men's Accessories 5", + }, + }, + } + `); + expect(getFilterByPath(filters, '1.1.1')).toMatchInlineSnapshot(` + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "key": "category.keyword", + "negate": false, + "params": Object { + "query": "Men's Accessories 4", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "category.keyword": "Men's Accessories 4", + }, + }, + } + `); + expect(getFilterByPath(filters, '1.1')).toMatchInlineSnapshot(` + Array [ + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "key": "category.keyword", + "negate": false, + "params": Object { + "query": "Men's Accessories 3", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "category.keyword": "Men's Accessories 3", + }, + }, + }, + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "key": "category.keyword", + "negate": false, + "params": Object { + "query": "Men's Accessories 4", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "category.keyword": "Men's Accessories 4", + }, + }, + }, + ] + `); + }); + }); + + describe('getConditionalOperationType', () => { + let filter: Filter; + let filtersWithOrRelationships: FilterItem; + let groupOfFilters: FilterItem; + + beforeAll(() => { + filter = filters[0]; + filtersWithOrRelationships = filters[1]; + groupOfFilters = filters[1].meta.params; + }); + + test('should return correct ConditionalOperationType', () => { + expect(getConditionalOperationType(filter)).toBeUndefined(); + expect(getConditionalOperationType(filtersWithOrRelationships)).toBe(ConditionTypes.OR); + expect(getConditionalOperationType(groupOfFilters)).toBe(ConditionTypes.AND); + }); + }); + + describe('getPathInArray', () => { + test('should return correct path in array from path', () => { + expect(getPathInArray('0')).toStrictEqual([0]); + expect(getPathInArray('1.1')).toStrictEqual([1, 1]); + expect(getPathInArray('1.0.2')).toStrictEqual([1, 0, 2]); + }); + }); + + describe('addFilter', () => { + const emptyFilter = buildEmptyFilter(false); + + test('should add filter into filters after zero element', () => { + const enlargedFilters = addFilter(filters, emptyFilter, '0', ConditionTypes.AND); + expect(getFilterByPath(enlargedFilters, '1')).toMatchInlineSnapshot(` + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": undefined, + "negate": false, + }, + } + `); + }); + }); + + describe('removeFilter', () => { + test('should remove filter from filters', () => { + const path = '1.1'; + const filterBeforeRemoved = getFilterByPath(filters, path); + const filtersAfterRemoveFilter = removeFilter(filters, path); + const filterObtainedAfterFilterRemovalFromFilters = getFilterByPath( + filtersAfterRemoveFilter, + path + ); + + expect(filterBeforeRemoved).not.toBe(filterObtainedAfterFilterRemovalFromFilters); + }); + }); + + describe('moveFilter', () => { + test('should move filter from "0" path to "2" path into filters', () => { + const filterBeforeMoving = getFilterByPath(filters, '0'); + const filtersAfterMovingFilter = moveFilter(filters, '0', '2', ConditionTypes.AND); + const filterObtainedAfterFilterMovingFilters = getFilterByPath(filtersAfterMovingFilter, '2'); + expect(filterBeforeMoving).toEqual(filterObtainedAfterFilterMovingFilters); + }); + }); + + describe('normalizeFilters', () => { + test('should normalize filter after removed filter', () => { + const dataNeedsNormalized = getDataThatNeedsNormalized(); + const dataAfterNormalized = getDataAfterNormalized(); + expect(normalizeFilters(dataNeedsNormalized)).toEqual(dataAfterNormalized); + }); + + test('should not normalize filter after removed filter', () => { + const dataNeedNotNormalized = getDataThatNeedNotNormalized(); + const dataAfterNormalized = getDataThatNeedNotNormalized(); + expect(normalizeFilters(dataNeedNotNormalized)).toEqual(dataAfterNormalized); + }); + }); +}); diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_utils.ts b/src/plugins/unified_search/public/filters_builder/filters_builder_utils.ts new file mode 100644 index 0000000000000..dbbc81824a479 --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_utils.ts @@ -0,0 +1,360 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataViewField } from '@kbn/data-views-plugin/common'; +import type { Filter } from '@kbn/es-query'; +import { cloneDeep } from 'lodash'; +import { ConditionTypes, getConditionalOperationType, isOrFilter, buildOrFilter } from '../utils'; +import type { FilterItem } from '../utils'; +import type { Operator } from '../filter_bar/filter_editor'; + +const PATH_SEPARATOR = '.'; + +/** + * The method returns the filter nesting identification number as an array. + * @param {string} path - variable is used to identify the filter and its nesting in the filter group. + */ +export const getPathInArray = (path: string) => path.split(PATH_SEPARATOR).map((i) => +i); + +const getGroupedFilters = (filter: FilterItem) => + Array.isArray(filter) ? filter : filter?.meta?.params; + +const doForFilterByPath = ( + filters: FilterItem[], + path: string, + action: (filter: FilterItem) => T +) => { + const pathArray = getPathInArray(path); + let f = filters[pathArray[0]]; + for (let i = 1, depth = pathArray.length; i < depth; i++) { + f = getGroupedFilters(f)[+pathArray[i]]; + } + return action(f); +}; + +const getContainerMetaByPath = (filters: FilterItem[], pathInArray: number[]) => { + let targetArray: FilterItem[] = filters; + let parentFilter: FilterItem | undefined; + let parentConditionType = ConditionTypes.AND; + + if (pathInArray.length > 1) { + parentFilter = getFilterByPath(filters, getParentFilterPath(pathInArray)); + parentConditionType = getConditionalOperationType(parentFilter) ?? parentConditionType; + targetArray = getGroupedFilters(parentFilter); + } + + return { + parentFilter, + targetArray, + parentConditionType, + }; +}; + +const getParentFilterPath = (pathInArray: number[]) => + pathInArray.slice(0, -1).join(PATH_SEPARATOR); + +/** + * The method corrects the positions of the filters after removing some filter from the filters. + * @param {FilterItem[]} filters - an array of filters that may contain filters that are incorrectly nested for later display in the UI. + */ +export const normalizeFilters = (filters: FilterItem[]) => { + const doRecursive = (f: FilterItem, parent: FilterItem) => { + if (Array.isArray(f)) { + return normalizeArray(f, parent); + } else if (isOrFilter(f)) { + return normalizeOr(f); + } + return f; + }; + + const normalizeArray = (filtersArray: FilterItem[], parent: FilterItem): FilterItem[] => { + const partiallyNormalized = filtersArray + .map((item) => { + const normalized = doRecursive(item, filtersArray); + + if (Array.isArray(normalized)) { + if (normalized.length === 1) { + return normalized[0]; + } + if (normalized.length === 0) { + return undefined; + } + } + return normalized; + }, []) + .filter(Boolean) as FilterItem[]; + + return Array.isArray(parent) ? partiallyNormalized.flat() : partiallyNormalized; + }; + + const normalizeOr = (orFilter: Filter): FilterItem => { + const orFilters = getGroupedFilters(orFilter); + if (orFilters.length < 2) { + return orFilters[0]; + } + + return { + ...orFilter, + meta: { + ...orFilter.meta, + params: doRecursive(orFilters, orFilter), + }, + }; + }; + + return normalizeArray(filters, filters) as Filter[]; +}; + +/** + * Find filter by path. + * @param {FilterItem[]} filters - filters in which the search for the desired filter will occur. + * @param {string} path - path to filter. + */ +export const getFilterByPath = (filters: FilterItem[], path: string) => + doForFilterByPath(filters, path, (f) => f); + +/** + * Method to add a filter to a specified location in a filter group. + * @param {Filter[]} filters - array of filters where the new filter will be added. + * @param {FilterItem} filter - new filter. + * @param {string} path - path to filter. + * @param {ConditionTypes} conditionalType - OR/AND relationships between filters. + */ +export const addFilter = ( + filters: Filter[], + filter: FilterItem, + path: string, + conditionalType: ConditionTypes +) => { + const newFilters = cloneDeep(filters); + const pathInArray = getPathInArray(path); + const { targetArray, parentConditionType } = getContainerMetaByPath(newFilters, pathInArray); + const selector = pathInArray[pathInArray.length - 1]; + + if (parentConditionType !== conditionalType) { + if (conditionalType === ConditionTypes.OR) { + targetArray.splice(selector, 1, buildOrFilter([targetArray[selector], filter])); + } + if (conditionalType === ConditionTypes.AND) { + targetArray.splice(selector, 1, [targetArray[selector], filter]); + } + } else { + targetArray.splice(selector + 1, 0, filter); + } + + return newFilters; +}; + +/** + * Remove filter from specified location. + * @param {Filter[]} filters - array of filters. + * @param {string} path - path to filter. + */ +export const removeFilter = (filters: Filter[], path: string) => { + const newFilters = cloneDeep(filters); + const pathInArray = getPathInArray(path); + const { targetArray } = getContainerMetaByPath(newFilters, pathInArray); + const selector = pathInArray[pathInArray.length - 1]; + + targetArray.splice(selector, 1); + + return normalizeFilters(newFilters); +}; + +/** + * Moving the filter on drag and drop. + * @param {Filter[]} filters - array of filters. + * @param {string} from - filter path before moving. + * @param {string} to - filter path where the filter will be moved. + * @param {ConditionTypes} conditionalType - OR/AND relationships between filters. + */ +export const moveFilter = ( + filters: Filter[], + from: string, + to: string, + conditionalType: ConditionTypes +) => { + const addFilterThenRemoveFilter = ( + source: Filter[], + addedFilter: FilterItem, + pathFrom: string, + pathTo: string, + conditional: ConditionTypes + ) => { + const newFiltersWithFilter = addFilter(source, addedFilter, pathTo, conditional); + return removeFilter(newFiltersWithFilter, pathFrom); + }; + + const removeFilterThenAddFilter = ( + source: Filter[], + removableFilter: FilterItem, + pathFrom: string, + pathTo: string, + conditional: ConditionTypes + ) => { + const newFiltersWithoutFilter = removeFilter(source, pathFrom); + return addFilter(newFiltersWithoutFilter, removableFilter, pathTo, conditional); + }; + + const newFilters = cloneDeep(filters); + const movingFilter = getFilterByPath(newFilters, from); + + const pathInArrayTo = getPathInArray(to); + const pathInArrayFrom = getPathInArray(from); + + if (pathInArrayTo.length === pathInArrayFrom.length) { + const filterPositionTo = pathInArrayTo.at(-1); + const filterPositionFrom = pathInArrayFrom.at(-1); + + const { parentConditionType } = getContainerMetaByPath(newFilters, pathInArrayTo); + const filterMovementDirection = Number(filterPositionTo) - Number(filterPositionFrom); + + if (filterMovementDirection === -1 && parentConditionType === conditionalType) { + return filters; + } + + if (filterMovementDirection >= -1) { + return addFilterThenRemoveFilter(newFilters, movingFilter, from, to, conditionalType); + } else { + return removeFilterThenAddFilter(newFilters, movingFilter, from, to, conditionalType); + } + } + + if (pathInArrayTo.length > pathInArrayFrom.length) { + return addFilterThenRemoveFilter(newFilters, movingFilter, from, to, conditionalType); + } else { + return removeFilterThenAddFilter(newFilters, movingFilter, from, to, conditionalType); + } +}; + +/** + * Method to update values inside filter. + * @param {Filter[]} filters - filter array + * @param {string} path - path to filter + * @param {DataViewField} field - DataViewField property inside a filter + * @param {Operator} operator - defines a relation by property and value + * @param {Filter['meta']['params']} params - filter value + */ +export const updateFilter = ( + filters: Filter[], + path: string, + field?: DataViewField, + operator?: Operator, + params?: Filter['meta']['params'] +) => { + const newFilters = [...filters]; + const changedFilter = getFilterByPath(newFilters, path) as Filter; + let filter = Object.assign({}, changedFilter); + + if (field && operator && params) { + if (Array.isArray(params)) { + filter = updateWithIsOneOfOperator(filter, operator, params); + } else { + filter = updateWithIsOperator(filter, operator, params); + } + } else if (field && operator) { + if (operator.type === 'exists') { + filter = updateWithExistsOperator(filter, operator); + } else { + filter = updateOperator(filter, operator); + } + } else { + filter = updateField(filter, field); + } + + const pathInArray = getPathInArray(path); + const { targetArray } = getContainerMetaByPath(newFilters, pathInArray); + const selector = pathInArray[pathInArray.length - 1]; + targetArray.splice(selector, 1, filter); + + return newFilters; +}; + +function updateField(filter: Filter, field?: DataViewField) { + return { + ...filter, + meta: { + ...filter.meta, + key: field?.name, + params: { query: undefined }, + value: undefined, + type: undefined, + }, + query: undefined, + }; +} + +function updateOperator(filter: Filter, operator?: Operator) { + return { + ...filter, + meta: { + ...filter.meta, + negate: operator?.negate, + type: operator?.type, + params: { ...filter.meta.params, query: undefined }, + value: undefined, + }, + query: { match_phrase: { field: filter.meta.key } }, + }; +} + +function updateWithExistsOperator(filter: Filter, operator?: Operator) { + return { + ...filter, + meta: { + ...filter.meta, + negate: operator?.negate, + type: operator?.type, + params: undefined, + value: 'exists', + }, + query: { exists: { field: filter.meta.key } }, + }; +} + +function updateWithIsOperator( + filter: Filter, + operator?: Operator, + params?: Filter['meta']['params'] +) { + return { + ...filter, + meta: { + ...filter.meta, + negate: operator?.negate, + type: operator?.type, + params: { ...filter.meta.params, query: params }, + }, + query: { match_phrase: { ...filter!.query?.match_phrase, [filter.meta.key!]: params } }, + }; +} + +function updateWithIsOneOfOperator( + filter: Filter, + operator?: Operator, + params?: Array +) { + return { + ...filter, + meta: { + ...filter.meta, + negate: operator?.negate, + type: operator?.type, + params, + }, + query: { + bool: { + minimum_should_match: 1, + ...filter!.query?.should, + should: params?.map((param) => { + return { match_phrase: { [filter.meta.key!]: param } }; + }), + }, + }, + }; +} diff --git a/src/plugins/unified_search/public/filters_builder/index.ts b/src/plugins/unified_search/public/filters_builder/index.ts new file mode 100644 index 0000000000000..0f430ca87aaac --- /dev/null +++ b/src/plugins/unified_search/public/filters_builder/index.ts @@ -0,0 +1,22 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { withSuspense } from '@kbn/shared-ux-utility'; + +/** + * The Lazily-loaded `FiltersBuilder` component. Consumers should use `React.Suspense` or + * the withSuspense` HOC to load this component. + */ +export const FiltersBuilderLazy = React.lazy(() => import('./filters_builder')); + +/** + * A `FiltersBuilder` component that is wrapped by the `withSuspense` HOC. This component can + * be used directly by consumers and will load the `FiltersBuilderLazy` component lazily with + * a predefined fallback and error boundary. + */ +export const FiltersBuilder = withSuspense(FiltersBuilderLazy); diff --git a/src/plugins/unified_search/public/index.ts b/src/plugins/unified_search/public/index.ts index 131b445017353..731396014d835 100755 --- a/src/plugins/unified_search/public/index.ts +++ b/src/plugins/unified_search/public/index.ts @@ -12,7 +12,11 @@ export type { IndexPatternSelectProps } from './index_pattern_select'; export type { QueryStringInputProps } from './query_string_input'; export { QueryStringInput } from './query_string_input'; export type { StatefulSearchBarProps, SearchBarProps } from './search_bar'; -export type { UnifiedSearchPublicPluginStart, UnifiedSearchPluginSetup } from './types'; +export type { + UnifiedSearchPublicPluginStart, + UnifiedSearchPluginSetup, + IUnifiedSearchPluginServices, +} from './types'; export { SearchBar } from './search_bar'; export type { FilterItemsProps } from './filter_bar'; export { FilterLabel, FilterItem, FilterItems } from './filter_bar'; diff --git a/src/plugins/unified_search/public/plugin.ts b/src/plugins/unified_search/public/plugin.ts index 05e22b035614d..e853e6b77e8e1 100755 --- a/src/plugins/unified_search/public/plugin.ts +++ b/src/plugins/unified_search/public/plugin.ts @@ -11,7 +11,7 @@ import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; import { UPDATE_FILTER_REFERENCES_TRIGGER, updateFilterReferencesTrigger } from './triggers'; import { ConfigSchema } from '../config'; -import { setIndexPatterns, setTheme, setOverlays, setAutocomplete } from './services'; +import { setIndexPatterns, setTheme, setOverlays } from './services'; import { AutocompleteService } from './autocomplete/autocomplete_service'; import { createSearchBar } from './search_bar'; import { createIndexPatternSelect } from './index_pattern_select'; @@ -70,7 +70,6 @@ export class UnifiedSearchPublicPlugin setOverlays(core.overlays); setIndexPatterns(dataViews); const autocompleteStart = this.autocomplete.start(); - setAutocomplete(autocompleteStart); const SearchBar = createSearchBar({ core, @@ -78,6 +77,9 @@ export class UnifiedSearchPublicPlugin storage: this.storage, usageCollection: this.usageCollection, isScreenshotMode: Boolean(screenshotMode?.isScreenshotMode()), + unifiedSearch: { + autocomplete: autocompleteStart, + }, }); uiActions.addTriggerAction( diff --git a/src/plugins/unified_search/public/query_string_input/filter_editor_wrapper.tsx b/src/plugins/unified_search/public/query_string_input/filter_editor_wrapper.tsx index dd106607353f2..a0e36688a2f8b 100644 --- a/src/plugins/unified_search/public/query_string_input/filter_editor_wrapper.tsx +++ b/src/plugins/unified_search/public/query_string_input/filter_editor_wrapper.tsx @@ -11,8 +11,8 @@ import { Filter, buildEmptyFilter } from '@kbn/es-query'; import { METRIC_TYPE } from '@kbn/analytics'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; -import { IDataPluginServices } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; +import type { IUnifiedSearchPluginServices } from '../types'; import { FILTER_EDITOR_WIDTH } from '../filter_bar/filter_item/filter_item'; import { FilterEditor } from '../filter_bar/filter_editor'; import { fetchIndexPatterns } from './fetch_index_patterns'; @@ -32,7 +32,7 @@ export const FilterEditorWrapper = React.memo(function FilterEditorWrapper({ closePopover, onFiltersUpdated, }: FilterEditorWrapperProps) { - const kibana = useKibana(); + const kibana = useKibana(); const { uiSettings, data, usageCollection, appName } = kibana.services; const reportUiCounter = usageCollection?.reportUiCounter.bind(usageCollection, appName); const [dataViews, setDataviews] = useState([]); diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx index 2b5dbf4999af1..4a921c3a1d177 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx @@ -29,7 +29,8 @@ import { import { METRIC_TYPE } from '@kbn/analytics'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { KIBANA_USER_QUERY_LANGUAGE_KEY, UI_SETTINGS } from '@kbn/data-plugin/common'; -import type { IDataPluginServices, SavedQueryService, SavedQuery } from '@kbn/data-plugin/public'; +import type { SavedQueryService, SavedQuery } from '@kbn/data-plugin/public'; +import type { IUnifiedSearchPluginServices } from '../types'; import { fromUser } from './from_user'; import { QueryLanguageSwitcher } from './language_switcher'; import { FilterPanelOption } from '../types'; @@ -88,7 +89,7 @@ export function QueryBarMenuPanels({ onQueryChange, setRenderedComponent, }: QueryBarMenuPanelsProps) { - const kibana = useKibana(); + const kibana = useKibana(); const { appName, usageCollection, uiSettings, http, storage } = kibana.services; const reportUiCounter = usageCollection?.reportUiCounter.bind(usageCollection, appName); const cancelPendingListingRequest = useRef<() => void>(() => {}); diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx index 1f879ebcae9a8..052e0ab7b32c8 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx @@ -20,7 +20,6 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { I18nProvider } from '@kbn/i18n-react'; import { stubIndexPattern } from '@kbn/data-plugin/public/stubs'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; -import { setAutocomplete } from '../services'; import { unifiedSearchPluginMock } from '../mocks'; const startMock = coreMock.createStart(); @@ -96,6 +95,7 @@ function wrapQueryBarTopRowInContext(testProps: any) { const services = { ...startMock, + unifiedSearch: unifiedSearchPluginMock.createStartContract(), data: dataPluginMock.createStartContract(), appName: 'discover', storage: createMockStorage(), @@ -120,11 +120,6 @@ describe('QueryBarTopRowTopRow', () => { jest.clearAllMocks(); }); - beforeEach(() => { - const autocompleteStart = unifiedSearchPluginMock.createStartContract(); - setAutocomplete(autocompleteStart.autocomplete); - }); - it('Should render query and time picker', () => { const { getByText, getByTestId } = render( wrapQueryBarTopRowInContext({ diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index ef6f09f679de2..c0848f630daa8 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -26,12 +26,13 @@ import { useIsWithinBreakpoints, EuiSuperUpdateButton, } from '@elastic/eui'; -import { IDataPluginServices, TimeHistoryContract, getQueryLog } from '@kbn/data-plugin/public'; +import { TimeHistoryContract, getQueryLog } from '@kbn/data-plugin/public'; import { i18n } from '@kbn/i18n'; import { DataView } from '@kbn/data-views-plugin/public'; import type { PersistedLog } from '@kbn/data-plugin/public'; import { useKibana, withKibana } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import type { IUnifiedSearchPluginServices } from '../types'; import QueryStringInputUI from './query_string_input'; import { NoDataPopover } from './no_data_popover'; import { shallowEqual } from '../utils/shallow_equal'; @@ -164,7 +165,7 @@ export const QueryBarTopRow = React.memo( const [isDateRangeInvalid, setIsDateRangeInvalid] = useState(false); const [isQueryInputFocused, setIsQueryInputFocused] = useState(false); - const kibana = useKibana(); + const kibana = useKibana(); const { uiSettings, storage, appName } = kibana.services; const isQueryLangSelected = props.query && !isOfQueryType(props.query); diff --git a/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx b/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx index 7437bf5fd4ece..41060aaecb3df 100644 --- a/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx @@ -27,12 +27,9 @@ import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { stubIndexPattern } from '@kbn/data-plugin/public/stubs'; import { KibanaContextProvider, withKibana } from '@kbn/kibana-react-plugin/public'; - -import { setAutocomplete } from '../services'; import { unifiedSearchPluginMock } from '../mocks'; jest.useFakeTimers(); - const startMock = coreMock.createStart(); const noop = () => { @@ -71,6 +68,7 @@ const QueryStringInput = withKibana(QueryStringInputUI); function wrapQueryStringInputInContext(testProps: any, storage?: any) { const services = { ...startMock, + unifiedSearch: unifiedSearchPluginMock.createStartContract(), data: dataPluginMock.createStartContract(), appName: testProps.appName || 'test', storage: storage || createMockStorage(), @@ -95,11 +93,6 @@ describe('QueryStringInput', () => { jest.clearAllMocks(); }); - beforeEach(() => { - const autocompleteStart = unifiedSearchPluginMock.createStartContract(); - setAutocomplete(autocompleteStart.autocomplete); - }); - it('Should render the given query', async () => { const { getByText } = render( wrapQueryStringInputInContext({ diff --git a/src/plugins/unified_search/public/query_string_input/query_string_input.tsx b/src/plugins/unified_search/public/query_string_input/query_string_input.tsx index c37e050b0823a..84a12d8c63200 100644 --- a/src/plugins/unified_search/public/query_string_input/query_string_input.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_string_input.tsx @@ -30,7 +30,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { compact, debounce, isEmpty, isEqual, isFunction } from 'lodash'; import { Toast } from '@kbn/core/public'; import type { Query } from '@kbn/es-query'; -import { IDataPluginServices, getQueryLog } from '@kbn/data-plugin/public'; +import { getQueryLog } from '@kbn/data-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; import type { PersistedLog } from '@kbn/data-plugin/public'; import { getFieldSubtypeNested, KIBANA_USER_QUERY_LANGUAGE_KEY } from '@kbn/data-plugin/common'; @@ -41,11 +41,12 @@ import { fromUser } from './from_user'; import { fetchIndexPatterns } from './fetch_index_patterns'; import { QueryLanguageSwitcher } from './language_switcher'; import type { SuggestionsListSize } from '../typeahead/suggestions_component'; +import type { IUnifiedSearchPluginServices } from '../types'; import { SuggestionsComponent } from '../typeahead'; import { onRaf } from '../utils'; import { FilterButtonGroup } from '../filter_bar/filter_button_group/filter_button_group'; import { QuerySuggestion, QuerySuggestionTypes } from '../autocomplete'; -import { getTheme, getAutocomplete } from '../services'; +import { getTheme } from '../services'; import './query_string_input.scss'; export interface QueryStringInputProps { @@ -93,7 +94,7 @@ export interface QueryStringInputProps { } interface Props extends QueryStringInputProps { - kibana: KibanaReactContextValue; + kibana: KibanaReactContextValue; } interface State { @@ -202,7 +203,9 @@ export default class QueryStringInputUI extends PureComponent { const queryString = this.getQueryString(); const recentSearchSuggestions = this.getRecentSearchSuggestions(queryString); - const hasQuerySuggestions = getAutocomplete().hasQuerySuggestions(language); + const hasQuerySuggestions = await this.services.unifiedSearch.autocomplete.hasQuerySuggestions( + language + ); if ( !hasQuerySuggestions || @@ -223,7 +226,7 @@ export default class QueryStringInputUI extends PureComponent { if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); const suggestions = - (await getAutocomplete().getQuerySuggestions({ + (await this.services.unifiedSearch.autocomplete.getQuerySuggestions({ language, indexPatterns, query: queryString, diff --git a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx index 9b6bb6707ab49..54343ec245efe 100644 --- a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx +++ b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx @@ -9,7 +9,6 @@ import React, { useRef, memo, useEffect, useState, useCallback } from 'react'; import classNames from 'classnames'; import { EsqlLang, monaco } from '@kbn/monaco'; -import { IDataPluginServices } from '@kbn/data-plugin/public'; import type { AggregateQuery } from '@kbn/es-query'; import { getAggregateQueryMode } from '@kbn/es-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; @@ -47,6 +46,7 @@ import { EditorFooter } from './editor_footer'; import { ResizableButton } from './resizable_button'; import './overwrite.scss'; +import { IUnifiedSearchPluginServices } from '../../types'; export interface TextBasedLanguagesEditorProps { query: AggregateQuery; @@ -106,7 +106,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ Array<{ startLineNumber: number; message: string }> >([]); const [documentationSections, setDocumentationSections] = useState(); - const kibana = useKibana(); + const kibana = useKibana(); const { uiSettings } = kibana.services; const styles = textBasedLanguagedEditorStyles( diff --git a/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.tsx b/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.tsx index 6200af754507a..15f5295e5ee36 100644 --- a/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.tsx +++ b/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.tsx @@ -27,9 +27,10 @@ import React, { useCallback, useEffect, useState, useRef } from 'react'; import { css } from '@emotion/react'; import { sortBy } from 'lodash'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { IDataPluginServices, SavedQuery, SavedQueryService } from '@kbn/data-plugin/public'; +import { SavedQuery, SavedQueryService } from '@kbn/data-plugin/public'; import type { SavedQueryAttributes } from '@kbn/data-plugin/common'; import './saved_query_management_list.scss'; +import type { IUnifiedSearchPluginServices } from '../types'; export interface SavedQueryManagementListProps { showSaveQuery?: boolean; @@ -120,7 +121,7 @@ export function SavedQueryManagementList({ onClose, hasFiltersOrQuery, }: SavedQueryManagementListProps) { - const kibana = useKibana(); + const kibana = useKibana(); const [savedQueries, setSavedQueries] = useState([] as SavedQuery[]); const [selectedSavedQuery, setSelectedSavedQuery] = useState(null as SavedQuery | null); const [toBeDeletedSavedQuery, setToBeDeletedSavedQuery] = useState(null as SavedQuery | null); diff --git a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx index 39980f03e3cbe..a4df4d0f1a76e 100644 --- a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx @@ -21,13 +21,15 @@ import { useFilterManager } from './lib/use_filter_manager'; import { useTimefilter } from './lib/use_timefilter'; import { useSavedQuery } from './lib/use_saved_query'; import { useQueryStringManager } from './lib/use_query_string_manager'; +import { UnifiedSearchPublicPluginStart } from '../types'; interface StatefulSearchBarDeps { core: CoreStart; - data: Omit; + data: DataPublicPluginStart; storage: IStorageWrapper; usageCollection?: UsageCollectionSetup; isScreenshotMode?: boolean; + unifiedSearch: Omit; } export type StatefulSearchBarProps = @@ -127,6 +129,7 @@ export function createSearchBar({ data, usageCollection, isScreenshotMode = false, + unifiedSearch, }: StatefulSearchBarDeps) { // App name should come from the core application service. // Until it's available, we'll ask the user to provide it for the pre-wired component. @@ -179,6 +182,7 @@ export function createSearchBar({ data, storage, usageCollection, + unifiedSearch, ...core, }} > diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index ecfbb388081ba..8a6396d993973 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -19,9 +19,9 @@ import { Query, Filter, TimeRange, AggregateQuery, isOfQueryType } from '@kbn/es import { withKibana, KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; import type { TimeHistoryContract, SavedQuery } from '@kbn/data-plugin/public'; import type { SavedQueryAttributes } from '@kbn/data-plugin/common'; -import { IDataPluginServices } from '@kbn/data-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; +import type { IUnifiedSearchPluginServices } from '../types'; import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form'; import { SavedQueryManagementList } from '../saved_query_management'; import { QueryBarMenu, QueryBarMenuProps } from '../query_string_input/query_bar_menu'; @@ -32,7 +32,7 @@ import type { SuggestionsListSize } from '../typeahead/suggestions_component'; import { searchBarStyles } from './search_bar.styles'; export interface SearchBarInjectedDeps { - kibana: KibanaReactContextValue; + kibana: KibanaReactContextValue; intl: InjectedIntl; timeHistory?: TimeHistoryContract; // Filter bar diff --git a/src/plugins/unified_search/public/services.ts b/src/plugins/unified_search/public/services.ts index f67801dd37730..4f8937baf8fdf 100644 --- a/src/plugins/unified_search/public/services.ts +++ b/src/plugins/unified_search/public/services.ts @@ -9,7 +9,6 @@ import { ThemeServiceStart, OverlayStart } from '@kbn/core/public'; import { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; -import { AutocompleteStart } from '.'; export const [getIndexPatterns, setIndexPatterns] = createGetterSetter('IndexPatterns'); @@ -17,6 +16,3 @@ export const [getIndexPatterns, setIndexPatterns] = export const [getTheme, setTheme] = createGetterSetter('Theme'); export const [getOverlays, setOverlays] = createGetterSetter('Overlays'); - -export const [getAutocomplete, setAutocomplete] = - createGetterSetter('Autocomplete'); diff --git a/src/plugins/unified_search/public/types.ts b/src/plugins/unified_search/public/types.ts index 3189e7cf32d08..246fc87114db4 100755 --- a/src/plugins/unified_search/public/types.ts +++ b/src/plugins/unified_search/public/types.ts @@ -11,8 +11,10 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { ScreenshotModePluginStart } from '@kbn/screenshot-mode-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { UsageCollectionSetup, UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { Query, AggregateQuery } from '@kbn/es-query'; +import { CoreStart } from '@kbn/core/public'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; import type { IndexPatternSelectProps, StatefulSearchBarProps } from '.'; @@ -56,7 +58,7 @@ export interface UnifiedSearchPublicPluginStart { autocomplete: AutocompleteStart; /** * prewired UI components - * {@link DataPublicPluginStartUi} + * {@link UnifiedSearchPublicPluginStartUi} */ ui: UnifiedSearchPublicPluginStartUi; } @@ -70,3 +72,18 @@ export type FilterPanelOption = | 'negateFilter' | 'disableFilter' | 'deleteFilter'; + +export interface IUnifiedSearchPluginServices extends Partial { + unifiedSearch: { + autocomplete: AutocompleteStart; + }; + appName: string; + uiSettings: CoreStart['uiSettings']; + savedObjects: CoreStart['savedObjects']; + notifications: CoreStart['notifications']; + application: CoreStart['application']; + http: CoreStart['http']; + storage: IStorageWrapper; + data: DataPublicPluginStart; + usageCollection?: UsageCollectionStart; +} diff --git a/src/plugins/unified_search/public/utils/index.ts b/src/plugins/unified_search/public/utils/index.ts index 5dffd3798399d..395304c48a914 100644 --- a/src/plugins/unified_search/public/utils/index.ts +++ b/src/plugins/unified_search/public/utils/index.ts @@ -8,3 +8,11 @@ export { onRaf } from './on_raf'; export { shallowEqual } from './shallow_equal'; + +export type { FilterItem } from './or_filter'; +export { + ConditionTypes, + isOrFilter, + getConditionalOperationType, + buildOrFilter, +} from './or_filter'; diff --git a/src/plugins/unified_search/public/utils/or_filter.ts b/src/plugins/unified_search/public/utils/or_filter.ts new file mode 100644 index 0000000000000..419e4f04d74ae --- /dev/null +++ b/src/plugins/unified_search/public/utils/or_filter.ts @@ -0,0 +1,47 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// Methods from this file will be removed after they are moved to the package +import { buildEmptyFilter, Filter } from '@kbn/es-query'; + +export enum ConditionTypes { + OR = 'OR', + AND = 'AND', +} + +/** @internal **/ +export type FilterItem = Filter | FilterItem[]; + +/** to: @kbn/es-query **/ +export const isOrFilter = (filter: Filter) => Boolean(filter?.meta?.type === 'OR'); + +/** + * Defines a conditional operation type (AND/OR) from the filter otherwise returns undefined. + * @param {FilterItem} filter + */ +export const getConditionalOperationType = (filter: FilterItem) => { + if (Array.isArray(filter)) { + return ConditionTypes.AND; + } else if (isOrFilter(filter)) { + return ConditionTypes.OR; + } +}; + +/** to: @kbn/es-query **/ +export const buildOrFilter = (filters: FilterItem) => { + const filter = buildEmptyFilter(false); + + return { + ...filter, + meta: { + ...filter.meta, + type: 'OR', + params: filters, + }, + }; +}; diff --git a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index 77a56eadee526..3f95b6adf6b45 100644 --- a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -20,8 +20,8 @@ import { i18n } from '@kbn/i18n'; import classNames from 'classnames'; import { FormattedMessage } from '@kbn/i18n-react'; import { connect } from 'react-redux'; -import { IDataPluginServices } from '@kbn/data-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { IUnifiedSearchPluginServices } from '@kbn/unified-search-plugin/public/types'; import { GraphState, hasDatasourceSelector, @@ -75,7 +75,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { const { onFillWorkspace, onOpenFieldPicker, onIndexPatternSelected, hasDatasource, hasFields } = props; - const kibana = useKibana(); + const kibana = useKibana(); const { services, overlays } = kibana; const { savedObjects, uiSettings, application, data } = services; const [hasDataViews, setHasDataViews] = useState(true); diff --git a/x-pack/plugins/graph/public/components/search_bar.test.tsx b/x-pack/plugins/graph/public/components/search_bar.test.tsx index 3fbf7222d1529..de9598b52087e 100644 --- a/x-pack/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.test.tsx @@ -19,8 +19,6 @@ import { import { act } from 'react-dom/test-utils'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { setAutocomplete } from '@kbn/unified-search-plugin/public/services'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { I18nProvider, InjectedIntl } from '@kbn/i18n-react'; @@ -106,11 +104,6 @@ describe('search_bar', () => { }, }; - beforeEach(() => { - const autocompleteStart = unifiedSearchPluginMock.createStartContract(); - setAutocomplete(autocompleteStart.autocomplete); - }); - beforeEach(() => { store = createMockGraphStore({ sagas: [submitSearchSaga], diff --git a/x-pack/plugins/graph/public/components/search_bar.tsx b/x-pack/plugins/graph/public/components/search_bar.tsx index 30f3fad82dafc..9aa05f64754cd 100644 --- a/x-pack/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.tsx @@ -12,9 +12,9 @@ import { i18n } from '@kbn/i18n'; import { connect } from 'react-redux'; import { toElasticsearchQuery, fromKueryExpression, Query } from '@kbn/es-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { IDataPluginServices } from '@kbn/data-plugin/public'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; +import { IUnifiedSearchPluginServices } from '@kbn/unified-search-plugin/public/types'; import { IndexPatternSavedObject, IndexPatternProvider, WorkspaceField } from '../types'; import { openSourceModal } from '../services/source_modal'; import { @@ -95,7 +95,7 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) fetchPattern(); }, [currentDatasource, indexPatternProvider, onIndexPatternChange]); - const kibana = useKibana(); + const kibana = useKibana(); const { services, overlays } = kibana; const { savedObjects, uiSettings } = services; if (!overlays) return null; diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx index fadf1767b1db6..b9e95a2ee837e 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx @@ -15,8 +15,6 @@ import { FilterManager } from '@kbn/data-plugin/public'; import { SearchBar } from '@kbn/unified-search-plugin/public'; import type { QueryBarComponentProps } from '.'; import { QueryBar } from '.'; -import { setAutocomplete } from '@kbn/unified-search-plugin/public/services'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings; @@ -275,11 +273,6 @@ describe('QueryBar ', () => { }); describe('#onSavedQueryUpdated', () => { - beforeEach(() => { - const autocompleteStart = unifiedSearchPluginMock.createStartContract(); - setAutocomplete(autocompleteStart.autocomplete); - }); - test('is only reference that changed when dataProviders props get updated', async () => { await act(async () => { const wrapper = await getWrapper( diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx index 385449d8754b6..c1c18c4c3cd3c 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx @@ -7,7 +7,7 @@ import React, { memo, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker } from '@elastic/eui'; -import type { IDataPluginServices } from '@kbn/data-plugin/public'; +import type { IUnifiedSearchPluginServices } from '@kbn/unified-search-plugin/public'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import type { EuiSuperDatePickerRecentRange } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; @@ -16,7 +16,6 @@ import type { OnRefreshChangeProps, } from '@elastic/eui/src/components/date_picker/types'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; - import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; import { useActionHistoryUrlParams } from './use_action_history_url_params'; @@ -52,7 +51,7 @@ export const ActionLogDateRangePicker = memo( }) => { const { startDate: startDateFromUrl, endDate: endDateFromUrl } = useActionHistoryUrlParams(); const getTestId = useTestIdGenerator('response-actions-list'); - const kibana = useKibana(); + const kibana = useKibana(); const { uiSettings } = kibana.services; const [commonlyUsedRanges] = useState(() => { return ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx index a1fcefceadfb5..3cf7acdef93fb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx @@ -18,8 +18,6 @@ import { FilterStateStore } from '@kbn/es-query'; import { FilterManager } from '@kbn/data-plugin/public'; import { mockDataProviders } from '../data_providers/mock/mock_data_providers'; import { buildGlobalQuery } from '../helpers'; -import { setAutocomplete } from '@kbn/unified-search-plugin/public/services'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import type { QueryBarTimelineComponentProps } from '.'; import { QueryBarTimeline, getDataProviderFilter, TIMELINE_FILTER_DROP_AREA } from '.'; @@ -182,11 +180,6 @@ describe('Timeline QueryBar ', () => { }); describe('#onSavedQuery', () => { - beforeEach(() => { - const autocompleteStart = unifiedSearchPluginMock.createStartContract(); - setAutocomplete(autocompleteStart.autocomplete); - }); - test('is only reference that changed when dataProviders props get updated', async () => { const Proxy = (props: QueryBarTimelineComponentProps) => ( diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.test.tsx index f3808984137df..b3aa4714fa664 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.test.tsx @@ -8,15 +8,8 @@ import React from 'react'; import { OverviewPageComponent } from './overview'; import { render } from '../lib/helper/rtl_helpers'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; -import { setAutocomplete } from '@kbn/unified-search-plugin/public/services'; describe('MonitorPage', () => { - beforeEach(() => { - const autocompleteStart = unifiedSearchPluginMock.createStartContract(); - setAutocomplete(autocompleteStart.autocomplete); - }); - it('renders expected elements for valid props', async () => { const { findByText, findByPlaceholderText } = render(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/query_bar/query_bar.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/query_bar/query_bar.test.tsx index bb05a23feb174..2ab2795e138bd 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/query_bar/query_bar.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/query_bar/query_bar.test.tsx @@ -13,9 +13,8 @@ import userEvent from '@testing-library/user-event'; import { FilterManager } from '@kbn/data-plugin/public'; import { coreMock } from '@kbn/core/public/mocks'; -import { TestProvidersComponent, unifiedSearch } from '../../../../common/mocks/test_providers'; +import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; import { getByTestSubj } from '../../../../../common/test/utils'; -import { setAutocomplete } from '@kbn/unified-search-plugin/public/services'; const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings; @@ -27,10 +26,6 @@ describe('QueryBar ', () => { const onSavedQuery = jest.fn(); const onChangedQuery = jest.fn(); - beforeEach(() => { - setAutocomplete(unifiedSearch.autocomplete); - }); - beforeEach(async () => { await act(async () => { render( From 6e753338879c9e910867395c41c148d0b65856ab Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Tue, 13 Sep 2022 10:49:19 -0400 Subject: [PATCH 19/28] Revert "[cft] Temporarily disable cloud deployments (#140538)" (#140622) This reverts commit e741176614c09354cd96f3feced7249a55326b02. --- .buildkite/scripts/steps/cloud/build_and_deploy.sh | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.buildkite/scripts/steps/cloud/build_and_deploy.sh b/.buildkite/scripts/steps/cloud/build_and_deploy.sh index f9b2a3fc928d2..017e25bb28850 100755 --- a/.buildkite/scripts/steps/cloud/build_and_deploy.sh +++ b/.buildkite/scripts/steps/cloud/build_and_deploy.sh @@ -2,14 +2,6 @@ set -euo pipefail -echo "Cloud deployments have been temporarily disabled. We're investigating, status updates will be posted in #kibana-operations." -cat << EOF | buildkite-agent annotate --style "error" --context cloud - ### Cloud Deployment - - Cloud deployments have been temporarily disabled. We're investigating, status updates will be posted in #kibana-operations. -EOF -exit 0 - source .buildkite/scripts/common/util.sh .buildkite/scripts/bootstrap.sh From 2f1f9e0b5d4f02cbd0ade21f43b90095c3282f55 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 13 Sep 2022 07:50:47 -0700 Subject: [PATCH 20/28] [Filters] Update filter meta to not include formatter functions (#136906) * Update filter meta to not include formatter functions * Fix failing tests * Update tests * Fix functional test & phrase mapper * Update scripted field handling * Look up field from getFilterField * Make meta optional * Fix failing tests * Fix dataTestSubj values * Fix scripted range filter display Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../filters/build_filters/get_filter_field.ts | 8 +-- .../build_filters/phrase_filter.test.ts | 10 +++ .../filters/build_filters/phrase_filter.ts | 7 +- .../src/filters/build_filters/range_filter.ts | 6 +- .../common/es_query/stubs/phrase_filter.ts | 6 +- .../common/es_query/stubs/range_filter.ts | 2 +- src/plugins/data/common/index.ts | 1 - src/plugins/data/common/types.ts | 6 -- .../lib/get_display_value.test.ts | 62 +++++++++++------ .../filter_manager/lib/get_display_value.ts | 41 ++++++++---- .../lib/map_and_flatten_filters.test.ts | 11 +--- .../filter_manager/lib/map_filter.test.ts | 17 ++--- .../lib/mappers/map_phrase.test.ts | 38 ++++++++--- .../filter_manager/lib/mappers/map_phrase.ts | 19 +++--- .../lib/mappers/map_phrases.test.ts | 61 +++++++++++++++++ .../filter_manager/lib/mappers/map_phrases.ts | 23 +++---- .../lib/mappers/map_range.test.ts | 66 +++++++++++++++++-- .../filter_manager/lib/mappers/map_range.ts | 46 +++++++------ .../context/services/context_state.test.ts | 4 +- .../filter_bar/filter_item/filter_item.tsx | 5 +- 20 files changed, 302 insertions(+), 137 deletions(-) create mode 100644 src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts diff --git a/packages/kbn-es-query/src/filters/build_filters/get_filter_field.ts b/packages/kbn-es-query/src/filters/build_filters/get_filter_field.ts index 9ae820cfea4e7..d8ef7e1106c0b 100644 --- a/packages/kbn-es-query/src/filters/build_filters/get_filter_field.ts +++ b/packages/kbn-es-query/src/filters/build_filters/get_filter_field.ts @@ -8,8 +8,8 @@ import { getExistsFilterField, isExistsFilter } from './exists_filter'; import { getPhrasesFilterField, isPhrasesFilter } from './phrases_filter'; -import { getPhraseFilterField, isPhraseFilter } from './phrase_filter'; -import { getRangeFilterField, isRangeFilter } from './range_filter'; +import { getPhraseFilterField, isPhraseFilter, isScriptedPhraseFilter } from './phrase_filter'; +import { getRangeFilterField, isRangeFilter, isScriptedRangeFilter } from './range_filter'; import type { Filter } from './types'; /** @internal */ @@ -17,13 +17,13 @@ export const getFilterField = (filter: Filter) => { if (isExistsFilter(filter)) { return getExistsFilterField(filter); } - if (isPhraseFilter(filter)) { + if (isPhraseFilter(filter) || isScriptedPhraseFilter(filter)) { return getPhraseFilterField(filter); } if (isPhrasesFilter(filter)) { return getPhrasesFilterField(filter); } - if (isRangeFilter(filter)) { + if (isRangeFilter(filter) || isScriptedRangeFilter(filter)) { return getRangeFilterField(filter); } diff --git a/packages/kbn-es-query/src/filters/build_filters/phrase_filter.test.ts b/packages/kbn-es-query/src/filters/build_filters/phrase_filter.test.ts index 7c7f7dd28f6ca..1088ba196840c 100644 --- a/packages/kbn-es-query/src/filters/build_filters/phrase_filter.test.ts +++ b/packages/kbn-es-query/src/filters/build_filters/phrase_filter.test.ts @@ -186,4 +186,14 @@ describe('isScriptedPhraseFilter', () => { expect(isScriptedPhraseFilter(filter)).toBe(true); expect(isPhraseFilter(unknownFilter)).toBe(false); }); + + it('should return false if the filter is a range filter', () => { + const filter: Filter = set({ meta: {} }, 'query.script.script.params', { + gt: 0, + lt: 100, + value: 100, + }) as Filter; + + expect(isScriptedPhraseFilter(filter)).toBe(false); + }); }); diff --git a/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts b/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts index 752dc8b338661..3bbf94cd0722a 100644 --- a/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts +++ b/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts @@ -10,6 +10,7 @@ import { get, has, isPlainObject } from 'lodash'; import type { Filter, FilterMeta } from './types'; import type { DataViewFieldBase, DataViewBase } from '../../es_query'; import { getConvertedValueForField } from './get_converted_value_for_field'; +import { hasRangeKeys } from './range_filter'; export type PhraseFilterValue = string | number | boolean; @@ -60,10 +61,12 @@ export const isPhraseFilter = (filter: Filter): filter is PhraseFilter => { * @public */ export const isScriptedPhraseFilter = (filter: Filter): filter is ScriptedPhraseFilter => - has(filter, 'query.script.script.params.value'); + has(filter, 'query.script.script.params.value') && + !hasRangeKeys(filter.query?.script?.script?.params); /** @internal */ -export const getPhraseFilterField = (filter: PhraseFilter) => { +export const getPhraseFilterField = (filter: PhraseFilter | ScriptedPhraseFilter) => { + if (filter.meta?.field) return filter.meta.field; const queryConfig = filter.query.match_phrase ?? filter.query.match ?? {}; return Object.keys(queryConfig)[0]; }; diff --git a/packages/kbn-es-query/src/filters/build_filters/range_filter.ts b/packages/kbn-es-query/src/filters/build_filters/range_filter.ts index 2ff43a854da23..e9bafade964b7 100644 --- a/packages/kbn-es-query/src/filters/build_filters/range_filter.ts +++ b/packages/kbn-es-query/src/filters/build_filters/range_filter.ts @@ -47,7 +47,7 @@ export interface RangeFilterParams { format?: string; } -const hasRangeKeys = (params: RangeFilterParams) => +export const hasRangeKeys = (params: RangeFilterParams) => Boolean( keys(params).find((key: string) => ['gte', 'gt', 'lte', 'lt', 'from', 'to'].includes(key)) ); @@ -108,8 +108,8 @@ export const isScriptedRangeFilter = (filter: Filter): filter is ScriptedRangeFi /** * @internal */ -export const getRangeFilterField = (filter: RangeFilter) => { - return filter.query.range && Object.keys(filter.query.range)[0]; +export const getRangeFilterField = (filter: RangeFilter | ScriptedRangeFilter) => { + return filter.meta?.field ?? (filter.query.range && Object.keys(filter.query.range)[0]); }; const formatValue = (params: any[]) => diff --git a/src/plugins/data/common/es_query/stubs/phrase_filter.ts b/src/plugins/data/common/es_query/stubs/phrase_filter.ts index 8c951b4d5d1fc..ef15d14750f9e 100644 --- a/src/plugins/data/common/es_query/stubs/phrase_filter.ts +++ b/src/plugins/data/common/es_query/stubs/phrase_filter.ts @@ -24,5 +24,9 @@ export const phraseFilter: PhraseFilter = { $state: { store: FilterStateStore.APP_STATE, }, - query: {}, + query: { + match_phrase: { + 'machine.os': 'ios', + }, + }, }; diff --git a/src/plugins/data/common/es_query/stubs/range_filter.ts b/src/plugins/data/common/es_query/stubs/range_filter.ts index 26c26afe8f545..a3799588b19f1 100644 --- a/src/plugins/data/common/es_query/stubs/range_filter.ts +++ b/src/plugins/data/common/es_query/stubs/range_filter.ts @@ -25,5 +25,5 @@ export const rangeFilter: RangeFilter = { $state: { store: FilterStateStore.APP_STATE, }, - query: { range: {} }, + query: { range: { bytes: { gt: 0, lt: 10 } } }, }; diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index 1a929df039ec0..f76b6b903fe95 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -33,7 +33,6 @@ export type { SavedQuery, SavedQueryAttributes, SavedQueryTimeFilter, - FilterValueFormatter, KbnFieldTypeOptions, Query, } from './types'; diff --git a/src/plugins/data/common/types.ts b/src/plugins/data/common/types.ts index 81b47735d8fe2..84c8f8b5a3fe1 100644 --- a/src/plugins/data/common/types.ts +++ b/src/plugins/data/common/types.ts @@ -20,9 +20,3 @@ export * from './kbn_field_types/types'; * not possible. */ export type GetConfigFn = (key: string, defaultOverride?: T) => T; - -type FilterFormatterFunction = (value: any) => string; -export interface FilterValueFormatter { - convert: FilterFormatterFunction; - getConverterFor: (type: string) => FilterFormatterFunction; -} diff --git a/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts b/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts index ffa8cb533bc5e..d32323d4d0b23 100644 --- a/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts @@ -7,7 +7,12 @@ */ import type { DataView } from '@kbn/data-views-plugin/public'; import { FilterStateStore, PhraseFilter } from '@kbn/es-query'; -import { stubIndexPattern, phraseFilter } from '../../../../common/stubs'; +import { + stubIndexPattern, + phraseFilter, + phrasesFilter, + rangeFilter, +} from '../../../../common/stubs'; import { getDisplayValueFromFilter, getFieldDisplayValueFromFilter } from './get_display_value'; import { FieldFormat } from '@kbn/field-formats-plugin/common'; @@ -17,38 +22,57 @@ describe('getDisplayValueFromFilter', () => { }); it('returns the value if string', () => { - phraseFilter.meta.value = 'abc'; - const displayValue = getDisplayValueFromFilter(phraseFilter, [stubIndexPattern]); + const filter = { ...phraseFilter, meta: { ...phraseFilter.meta, value: 'abc' } }; + const displayValue = getDisplayValueFromFilter(filter, [stubIndexPattern]); expect(displayValue).toBe('abc'); }); it('returns the value if undefined', () => { - phraseFilter.meta.value = undefined; - const displayValue = getDisplayValueFromFilter(phraseFilter, [stubIndexPattern]); + const filter = { + ...phraseFilter, + meta: { ...phraseFilter.meta, value: undefined, params: { query: undefined } }, + }; + const displayValue = getDisplayValueFromFilter(filter, [stubIndexPattern]); expect(displayValue).toBe(''); }); - it('calls the value function if provided', () => { - // The type of value currently doesn't match how it's used. Refactor needed. - phraseFilter.meta.value = jest.fn((x) => { - return 'abc'; - }) as any; + it('phrase filters without formatter', () => { jest.spyOn(stubIndexPattern, 'getFormatterForField').mockImplementation(() => undefined!); const displayValue = getDisplayValueFromFilter(phraseFilter, [stubIndexPattern]); - expect(displayValue).toBe('abc'); - expect(phraseFilter.meta.value).toHaveBeenCalledWith(undefined); + expect(displayValue).toBe('ios'); }); - it('calls the value function if provided, with formatter', () => { + it('phrase filters with formatter', () => { const mockFormatter = new (FieldFormat.from((value: string) => 'banana' + value))(); jest.spyOn(stubIndexPattern, 'getFormatterForField').mockImplementation(() => mockFormatter); - phraseFilter.meta.value = jest.fn((x) => { - return x.convert('abc'); - }) as any; const displayValue = getDisplayValueFromFilter(phraseFilter, [stubIndexPattern]); - expect(stubIndexPattern.getFormatterForField).toHaveBeenCalledTimes(1); - expect(phraseFilter.meta.value).toHaveBeenCalledWith(mockFormatter); - expect(displayValue).toBe('bananaabc'); + expect(displayValue).toBe('bananaios'); + }); + + it('phrases filters without formatter', () => { + jest.spyOn(stubIndexPattern, 'getFormatterForField').mockImplementation(() => undefined!); + const displayValue = getDisplayValueFromFilter(phrasesFilter, [stubIndexPattern]); + expect(displayValue).toBe('win xp, osx'); + }); + + it('phrases filters with formatter', () => { + const mockFormatter = new (FieldFormat.from((value: string) => 'banana' + value))(); + jest.spyOn(stubIndexPattern, 'getFormatterForField').mockImplementation(() => mockFormatter); + const displayValue = getDisplayValueFromFilter(phrasesFilter, [stubIndexPattern]); + expect(displayValue).toBe('bananawin xp, bananaosx'); + }); + + it('range filters without formatter', () => { + jest.spyOn(stubIndexPattern, 'getFormatterForField').mockImplementation(() => undefined!); + const displayValue = getDisplayValueFromFilter(rangeFilter, [stubIndexPattern]); + expect(displayValue).toBe('0 to 10'); + }); + + it('range filters with formatter', () => { + const mockFormatter = new (FieldFormat.from((value: string) => 'banana' + value))(); + jest.spyOn(stubIndexPattern, 'getFormatterForField').mockImplementation(() => mockFormatter); + const displayValue = getDisplayValueFromFilter(rangeFilter, [stubIndexPattern]); + expect(displayValue).toBe('banana0 to banana10'); }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts b/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts index 40bbe9a89c992..5543e0071b4d0 100644 --- a/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts +++ b/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts @@ -8,7 +8,18 @@ import { i18n } from '@kbn/i18n'; import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; -import { Filter } from '@kbn/es-query'; +import { + Filter, + isPhraseFilter, + isPhrasesFilter, + isRangeFilter, + isScriptedPhraseFilter, + isScriptedRangeFilter, + getFilterField, +} from '@kbn/es-query'; +import { getPhraseDisplayValue } from './mappers/map_phrase'; +import { getPhrasesDisplayValue } from './mappers/map_phrases'; +import { getRangeDisplayValue } from './mappers/map_range'; import { getIndexPatternFromFilter } from './get_index_pattern_from_filter'; function getValueFormatter(indexPattern?: DataView, key?: string) { @@ -29,22 +40,26 @@ function getValueFormatter(indexPattern?: DataView, key?: string) { } export function getFieldDisplayValueFromFilter(filter: Filter, indexPatterns: DataView[]): string { - const { key } = filter.meta; const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); if (!indexPattern) return ''; - const field = indexPattern.fields.find((f: DataViewField) => f.name === key); + + const fieldName = getFilterField(filter); + if (!fieldName) return ''; + + const field = indexPattern.fields.find((f: DataViewField) => f.name === fieldName); return field?.customLabel ?? ''; } export function getDisplayValueFromFilter(filter: Filter, indexPatterns: DataView[]): string { - const { key, value } = filter.meta; - if (typeof value === 'function') { - const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); - const valueFormatter = getValueFormatter(indexPattern, key); - // TODO: distinguish between FilterMeta which is serializable to mapped FilterMeta - // Where value can be a function. - return (value as any)(valueFormatter); - } else { - return value || ''; - } + const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); + const fieldName = getFilterField(filter); + const valueFormatter = getValueFormatter(indexPattern, fieldName); + + if (isPhraseFilter(filter) || isScriptedPhraseFilter(filter)) { + return getPhraseDisplayValue(filter, valueFormatter); + } else if (isPhrasesFilter(filter)) { + return getPhrasesDisplayValue(filter, valueFormatter); + } else if (isRangeFilter(filter) || isScriptedRangeFilter(filter)) { + return getRangeDisplayValue(filter, valueFormatter); + } else return filter.meta.value ?? ''; } diff --git a/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts index e7b4067881656..91b2ae8d3ada6 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts @@ -13,12 +13,6 @@ describe('filter manager utilities', () => { describe('mapAndFlattenFilters()', () => { let filters: unknown; - function getDisplayName(filter: Filter) { - return typeof filter.meta.value === 'function' - ? (filter.meta.value as any)() - : filter.meta.value; - } - beforeEach(() => { filters = [ null, @@ -51,11 +45,8 @@ describe('filter manager utilities', () => { expect(results[2].meta).toHaveProperty('key', 'query'); expect(results[2].meta).toHaveProperty('value', 'foo:bar'); expect(results[3].meta).toHaveProperty('key', 'bytes'); - expect(results[3].meta).toHaveProperty('value'); - expect(getDisplayName(results[3])).toBe('1024 to 2048'); + expect(results[3].meta).toHaveProperty('value', { gt: 1024, lt: 2048 }); expect(results[4].meta).toHaveProperty('key', '_type'); - expect(results[4].meta).toHaveProperty('value'); - expect(getDisplayName(results[4])).toBe('apache'); }); }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts index be94e69f74f9d..ff9c6d47660fd 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts @@ -7,27 +7,22 @@ */ import { mapFilter } from './map_filter'; -import type { Filter } from '@kbn/es-query'; +import type { Filter, PhraseFilter } from '@kbn/es-query'; +import { getDisplayValueFromFilter } from '../../..'; describe('filter manager utilities', () => { - function getDisplayName(filter: Filter) { - return typeof filter.meta.value === 'function' - ? (filter.meta.value as any)() - : filter.meta.value; - } - describe('mapFilter()', () => { test('should map query filters', async () => { const before = { meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } }, }; - const after = mapFilter(before as Filter); + const after = mapFilter(before as Filter) as PhraseFilter; expect(after).toHaveProperty('meta'); expect(after.meta).toHaveProperty('key', '_type'); expect(after.meta).toHaveProperty('value'); - expect(getDisplayName(after)).toBe('apache'); + expect(getDisplayValueFromFilter(after, [])).toBe('apache'); expect(after.meta).toHaveProperty('disabled', false); expect(after.meta).toHaveProperty('negate', false); }); @@ -42,7 +37,7 @@ describe('filter manager utilities', () => { expect(after).toHaveProperty('meta'); expect(after.meta).toHaveProperty('key', '@timestamp'); expect(after.meta).toHaveProperty('value'); - expect(getDisplayName(after)).toBe('exists'); + expect(getDisplayValueFromFilter(after, [])).toBe('exists'); expect(after.meta).toHaveProperty('disabled', false); expect(after.meta).toHaveProperty('negate', false); }); @@ -54,7 +49,7 @@ describe('filter manager utilities', () => { expect(after).toHaveProperty('meta'); expect(after.meta).toHaveProperty('key', 'query'); expect(after.meta).toHaveProperty('value'); - expect(getDisplayName(after)).toBe('{"test":{}}'); + expect(getDisplayValueFromFilter(after, [])).toBe('{"test":{}}'); expect(after.meta).toHaveProperty('disabled', false); expect(after.meta).toHaveProperty('negate', false); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts index c63a8f82f1704..88e819bb7f9a7 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts @@ -6,12 +6,13 @@ * Side Public License, v 1. */ -import { mapPhrase } from './map_phrase'; +import { getPhraseDisplayValue, mapPhrase } from './map_phrase'; import type { PhraseFilter, Filter } from '@kbn/es-query'; +import { FieldFormat } from '@kbn/field-formats-plugin/common'; describe('filter manager utilities', () => { describe('mapPhrase()', () => { - test('should return the key and value for matching filters', async () => { + test('should return the key for matching filters', async () => { const filter = { meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } }, @@ -19,13 +20,7 @@ describe('filter manager utilities', () => { const result = mapPhrase(filter); - expect(result).toHaveProperty('value'); expect(result).toHaveProperty('key', '_type'); - - if (result.value) { - const displayName = result.value(); - expect(displayName).toBe('apache'); - } }); test('should return undefined for none matching', async (done) => { @@ -42,4 +37,31 @@ describe('filter manager utilities', () => { } }); }); + + describe('getPhraseDisplayValue()', () => { + test('without formatter with value', () => { + const filter = { meta: { value: 'hello' } } as PhraseFilter; + const result = getPhraseDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`"hello"`); + }); + + test('without formatter empty value', () => { + const filter = { meta: { value: '' } } as PhraseFilter; + const result = getPhraseDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`""`); + }); + + test('without formatter with undefined value', () => { + const filter = { meta: { params: {} } } as PhraseFilter; + const result = getPhraseDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`""`); + }); + + test('with formatter', () => { + const filter = { meta: { value: 'hello' } } as PhraseFilter; + const formatter = { convert: (val) => `formatted ${val}` } as FieldFormat; + const result = getPhraseDisplayValue(filter, formatter); + expect(result).toMatchInlineSnapshot(`"formatted hello"`); + }); + }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts index 23cae0ee852ca..fe80db37ee36c 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts @@ -6,27 +6,27 @@ * Side Public License, v 1. */ +import type { Filter, PhraseFilter, ScriptedPhraseFilter } from '@kbn/es-query'; import { get } from 'lodash'; import { - PhraseFilter, getPhraseFilterValue, getPhraseFilterField, FILTERS, isScriptedPhraseFilter, - Filter, isPhraseFilter, } from '@kbn/es-query'; - -import { FilterValueFormatter } from '../../../../../common'; +import { FieldFormat } from '@kbn/field-formats-plugin/common'; const getScriptedPhraseValue = (filter: PhraseFilter) => get(filter, ['query', 'script', 'script', 'params', 'value']); -const getFormattedValueFn = (value: any) => { - return (formatter?: FilterValueFormatter) => { - return formatter ? formatter.convert(value) : value; - }; -}; +export function getPhraseDisplayValue( + filter: PhraseFilter | ScriptedPhraseFilter, + formatter?: FieldFormat +) { + const value = filter.meta.value ?? filter.meta.params.query; + return formatter?.convert(value) ?? value ?? ''; +} const getParams = (filter: PhraseFilter) => { const scriptedPhraseValue = getScriptedPhraseValue(filter); @@ -39,7 +39,6 @@ const getParams = (filter: PhraseFilter) => { key, params, type: FILTERS.PHRASE, - value: getFormattedValueFn(query), }; }; diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts new file mode 100644 index 0000000000000..4a219e23aff09 --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts @@ -0,0 +1,61 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { PhrasesFilter, Filter } from '@kbn/es-query'; +import { FILTERS } from '@kbn/es-query'; +import { getPhrasesDisplayValue, mapPhrases } from './map_phrases'; +import { FieldFormat } from '@kbn/field-formats-plugin/common'; + +describe('filter manager utilities', () => { + describe('mapPhrases()', () => { + test('should return the key and value for matching filters', async () => { + const filter = { + meta: { + type: FILTERS.PHRASES, + index: 'logstash-*', + key: '_type', + params: ['hello', 1, 'world'], + }, + } as PhrasesFilter; + + const result = mapPhrases(filter); + + expect(result).toHaveProperty('key', '_type'); + expect(result).toHaveProperty('value', ['hello', 1, 'world']); + }); + + test('should return undefined for none matching', async (done) => { + const filter = { + meta: { index: 'logstash-*' }, + query: { query_string: { query: 'foo:bar' } }, + } as Filter; + + try { + mapPhrases(filter); + } catch (e) { + expect(e).toBe(filter); + done(); + } + }); + }); + + describe('getPhrasesDisplayValue()', () => { + test('without formatter', () => { + const filter = { meta: { params: ['hello', 1, 'world'] } } as PhrasesFilter; + const result = getPhrasesDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`"hello, 1, world"`); + }); + + test('with formatter', () => { + const filter = { meta: { params: ['hello', 1, 'world'] } } as PhrasesFilter; + const formatter = { convert: (val) => `formatted ${val}` } as FieldFormat; + const result = getPhrasesDisplayValue(filter, formatter); + expect(result).toMatchInlineSnapshot(`"formatted hello, formatted 1, formatted world"`); + }); + }); +}); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts index 9ffdd3070e43a..48ca3852e715d 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts @@ -6,19 +6,16 @@ * Side Public License, v 1. */ -import { Filter, isPhrasesFilter } from '@kbn/es-query'; +import { Filter, PhrasesFilter, isPhrasesFilter } from '@kbn/es-query'; +import { FieldFormat } from '@kbn/field-formats-plugin/common'; -import { FilterValueFormatter } from '../../../../../common'; - -const getFormattedValueFn = (params: any) => { - return (formatter?: FilterValueFormatter) => { - return params - .map((v: any) => { - return formatter ? formatter.convert(v) : v; - }) - .join(', '); - }; -}; +export function getPhrasesDisplayValue(filter: PhrasesFilter, formatter?: FieldFormat) { + return filter.meta.params + .map((v: string) => { + return formatter?.convert(v) ?? v; + }) + .join(', '); +} export const mapPhrases = (filter: Filter) => { if (!isPhrasesFilter(filter)) { @@ -30,7 +27,7 @@ export const mapPhrases = (filter: Filter) => { return { type, key, - value: getFormattedValueFn(params), + value: params, params, }; }; diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts index 3b82ee6ef0f1c..065e3da189999 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { mapRange } from './map_range'; +import { getRangeDisplayValue, mapRange } from './map_range'; import { FilterMeta, RangeFilter, Filter } from '@kbn/es-query'; describe('filter manager utilities', () => { @@ -19,11 +19,7 @@ describe('filter manager utilities', () => { const result = mapRange(filter); expect(result).toHaveProperty('key', 'bytes'); - expect(result).toHaveProperty('value'); - if (result.value) { - const displayName = result.value(); - expect(displayName).toBe('1024 to 2048'); - } + expect(result).toHaveProperty('value', { gt: 1024, lt: 2048 }); }); test('should return undefined for none matching', async (done) => { @@ -41,4 +37,62 @@ describe('filter manager utilities', () => { } }); }); + + describe('getRangeDisplayValue()', () => { + test('gt & lt', () => { + const params = { gt: 10, lt: 100 }; + const filter = { meta: { params } } as RangeFilter; + const result = getRangeDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`"10 to 100"`); + }); + + test('gt & lte', () => { + const params = { gt: 20, lte: 200 }; + const filter = { meta: { params } } as RangeFilter; + const result = getRangeDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`"20 to 200"`); + }); + + test('gte & lt', () => { + const params = { gte: 'low', lt: 'high' }; + const filter = { meta: { params } } as RangeFilter; + const result = getRangeDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`"low to high"`); + }); + + test('gte & lte', () => { + const params = { gte: 40, lte: 400 }; + const filter = { meta: { params } } as RangeFilter; + const result = getRangeDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`"40 to 400"`); + }); + + test('gt', () => { + const params = { gt: 50 }; + const filter = { meta: { params } } as RangeFilter; + const result = getRangeDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`"50 to Infinity"`); + }); + + test('gte', () => { + const params = { gte: 60 }; + const filter = { meta: { params } } as RangeFilter; + const result = getRangeDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`"60 to Infinity"`); + }); + + test('lt', () => { + const params = { lt: 70 }; + const filter = { meta: { params } } as RangeFilter; + const result = getRangeDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`"-Infinity to 70"`); + }); + + test('lte', () => { + const params = { lte: 80 }; + const filter = { meta: { params } } as RangeFilter; + const result = getRangeDisplayValue(filter); + expect(result).toMatchInlineSnapshot(`"-Infinity to 80"`); + }); + }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts index c5fa5ccc89957..04eb67e792163 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts @@ -6,21 +6,27 @@ * Side Public License, v 1. */ -import { get, hasIn } from 'lodash'; -import { RangeFilter, isScriptedRangeFilter, isRangeFilter, Filter, FILTERS } from '@kbn/es-query'; - -import { FilterValueFormatter } from '../../../../../common'; - -const getFormattedValueFn = (left: any, right: any) => { - return (formatter?: FilterValueFormatter) => { - let displayValue = `${left} to ${right}`; - if (formatter) { - const convert = formatter.getConverterFor('text'); - displayValue = `${convert(left)} to ${convert(right)}`; - } - return displayValue; - }; -}; +import { get } from 'lodash'; +import { + ScriptedRangeFilter, + RangeFilter, + isScriptedRangeFilter, + isRangeFilter, + Filter, + FILTERS, +} from '@kbn/es-query'; +import { FieldFormat } from '@kbn/field-formats-plugin/common'; + +export function getRangeDisplayValue( + { meta: { params } }: RangeFilter | ScriptedRangeFilter, + formatter?: FieldFormat +) { + const left = params.gte ?? params.gt ?? -Infinity; + const right = params.lte ?? params.lt ?? Infinity; + if (!formatter) return `${left} to ${right}`; + const convert = formatter.getConverterFor('text'); + return `${convert(left)} to ${convert(right)}`; +} const getFirstRangeKey = (filter: RangeFilter) => filter.query.range && Object.keys(filter.query.range)[0]; @@ -33,15 +39,7 @@ function getParams(filter: RangeFilter) { ? get(filter.query, 'script.script.params') : getRangeByKey(filter, key); - let left = hasIn(params, 'gte') ? params.gte : params.gt; - if (left == null) left = -Infinity; - - let right = hasIn(params, 'lte') ? params.lte : params.lt; - if (right == null) right = Infinity; - - const value = getFormattedValueFn(left, right); - - return { type: FILTERS.RANGE, key, value, params }; + return { type: FILTERS.RANGE, key, value: params, params }; } export const isMapRangeFilter = (filter: any): filter is RangeFilter => diff --git a/src/plugins/discover/public/application/context/services/context_state.test.ts b/src/plugins/discover/public/application/context/services/context_state.test.ts index 2b071ad9f7fbd..ae1916158f85e 100644 --- a/src/plugins/discover/public/application/context/services/context_state.test.ts +++ b/src/plugins/discover/public/application/context/services/context_state.test.ts @@ -133,7 +133,7 @@ describe('Test Discover Context State', () => { "query": "jpg", }, "type": "phrase", - "value": [Function], + "value": undefined, }, "query": Object { "match_phrase": Object { @@ -157,7 +157,7 @@ describe('Test Discover Context State', () => { "query": "png", }, "type": "phrase", - "value": [Function], + "value": undefined, }, "query": Object { "match_phrase": Object { diff --git a/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx b/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx index 3692b19f7e440..32601f4590558 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx @@ -160,9 +160,8 @@ export function FilterItem(props: FilterItemProps) { function getDataTestSubj(labelConfig: LabelOptions) { const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; - const dataTestSubjValue = filter.meta.value - ? `filter-value-${isValidLabel(labelConfig) ? labelConfig.title : labelConfig.status}` - : ''; + const valueLabel = isValidLabel(labelConfig) ? labelConfig.title : labelConfig.status; + const dataTestSubjValue = valueLabel ? `filter-value-${valueLabel}` : ''; const dataTestSubjNegated = filter.meta.negate ? 'filter-negated' : ''; const dataTestSubjDisabled = `filter-${isDisabled(labelConfig) ? 'disabled' : 'enabled'}`; const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; From fd19ec33f7e1ea56117364bf0090a0784079d538 Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Tue, 13 Sep 2022 17:33:38 +0200 Subject: [PATCH 21/28] prevent component unmout when loading (#140608) --- .../plugins/security_solution/public/hosts/pages/hosts.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index 145b9ec56a344..9d6687a771f36 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -58,7 +58,6 @@ import { ID } from '../containers/hosts'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { LandingPageComponent } from '../../common/components/landing_page'; -import { Loader } from '../../common/components/loader'; import { hostNameExistsFilter } from '../../common/components/visualization_actions/utils'; /** @@ -125,7 +124,7 @@ const HostsComponent = () => { }, [dispatch] ); - const { indicesExist, indexPattern, selectedPatterns, loading } = useSourcererDataView(); + const { indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const [filterQuery, kqlError] = useMemo( () => convertToBuildEsQuery({ @@ -175,10 +174,6 @@ const HostsComponent = () => { [containerElement, onSkipFocusBeforeEventsTable, onSkipFocusAfterEventsTable] ); - if (loading) { - return ; - } - return ( <> {indicesExist ? ( From 24d4865c786cb5e0f747e5aa1f8d83ca964e58d7 Mon Sep 17 00:00:00 2001 From: Marshall Main <55718608+marshallmain@users.noreply.github.com> Date: Tue, 13 Sep 2022 09:09:04 -0700 Subject: [PATCH 22/28] [Security Solution][Alerts] Remove legacy rules schema (#137605) * Remove legacy rules schema * Remove RulesSchema from cypress test * Fix ml rule API test mock * Fix type error * Explicitly specify undefined fields in response mocks * Fix types * Remove undefined properties so expect doesn't mess it up * Use more specific types and rename newTransformValidate * Fix types in schema tests * Review comments - explicit return types, better removeServerGeneratedProperties * Re-add deleted test with modified assertion Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../schemas/request/rule_schemas.ts | 29 +- .../schemas/response/index.ts | 2 - .../response/rules_bulk_schema.test.ts | 13 +- .../schemas/response/rules_bulk_schema.ts | 4 +- .../schemas/response/rules_schema.mocks.ts | 92 ++- .../schemas/response/rules_schema.test.ts | 705 +----------------- .../schemas/response/rules_schema.ts | 366 --------- .../type_timeline_only_schema.test.ts | 66 -- .../response/type_timeline_only_schema.ts | 21 - .../security_solution/cypress/objects/rule.ts | 16 +- .../detection_engine/rules/__mocks__/api.ts | 16 +- .../containers/detection_engine/rules/api.ts | 12 +- .../routes/__mocks__/utils.ts | 14 +- .../routes/rules/create_rules_route.ts | 4 +- .../detection_engine/routes/rules/utils.ts | 8 +- .../routes/rules/validate.test.ts | 15 +- .../detection_engine/routes/rules/validate.ts | 19 +- .../rules/get_export_by_object_ids.test.ts | 7 + .../rules/get_export_by_object_ids.ts | 6 +- .../rules/get_export_details_ndjson.ts | 4 +- .../signals/__mocks__/es_results.ts | 19 + .../lib/detection_engine/signals/types.ts | 4 +- .../group10/perform_bulk_action.ts | 4 +- .../group10/update_rules.ts | 1 + .../group10/update_rules_bulk.ts | 10 +- .../group3/create_exceptions.ts | 7 +- .../utils/get_complex_rule.ts | 6 +- .../utils/get_complex_rule_output.ts | 6 +- .../utils/get_rule.ts | 4 +- .../utils/get_simple_ml_rule_output.ts | 16 +- .../utils/get_simple_rule_output.ts | 56 +- ...simple_rule_output_with_web_hook_action.ts | 6 +- .../get_simple_rule_output_without_rule_id.ts | 6 +- .../remove_server_generated_properties.ts | 27 +- .../utils/resolve_simple_rule_output.ts | 10 +- 35 files changed, 335 insertions(+), 1266 deletions(-) delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts index 46186479bf726..08e609938938c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts @@ -284,7 +284,7 @@ const { patch: threatMatchPatchParams, response: threatMatchResponseParams, } = buildAPISchemas(threatMatchRuleParams); -export { threatMatchCreateParams }; +export { threatMatchCreateParams, threatMatchResponseParams }; const queryRuleParams = { required: { @@ -307,7 +307,7 @@ const { response: queryResponseParams, } = buildAPISchemas(queryRuleParams); -export { queryCreateParams }; +export { queryCreateParams, queryResponseParams }; const savedQueryRuleParams = { required: { @@ -332,7 +332,7 @@ const { response: savedQueryResponseParams, } = buildAPISchemas(savedQueryRuleParams); -export { savedQueryCreateParams }; +export { savedQueryCreateParams, savedQueryResponseParams }; const thresholdRuleParams = { required: { @@ -356,7 +356,7 @@ const { response: thresholdResponseParams, } = buildAPISchemas(thresholdRuleParams); -export { thresholdCreateParams }; +export { thresholdCreateParams, thresholdResponseParams }; const machineLearningRuleParams = { required: { @@ -373,7 +373,7 @@ const { response: machineLearningResponseParams, } = buildAPISchemas(machineLearningRuleParams); -export { machineLearningCreateParams }; +export { machineLearningCreateParams, machineLearningResponseParams }; const newTermsRuleParams = { required: { @@ -397,7 +397,7 @@ const { response: newTermsResponseParams, } = buildAPISchemas(newTermsRuleParams); -export { newTermsCreateParams }; +export { newTermsCreateParams, newTermsResponseParams }; // --------------------------------------- // END type specific parameter definitions @@ -503,14 +503,27 @@ const responseOptionalFields = { execution_summary: RuleExecutionSummary, }; -export const fullResponseSchema = t.intersection([ +const sharedResponseSchema = t.intersection([ baseResponseParams, - responseTypeSpecific, t.exact(t.type(responseRequiredFields)), t.exact(t.partial(responseOptionalFields)), ]); +export type SharedResponseSchema = t.TypeOf; +export const fullResponseSchema = t.intersection([sharedResponseSchema, responseTypeSpecific]); export type FullResponseSchema = t.TypeOf; +// Convenience types for type specific responses +type ResponseSchema = SharedResponseSchema & T; +export type EqlResponseSchema = ResponseSchema>; +export type ThreatMatchResponseSchema = ResponseSchema>; +export type QueryResponseSchema = ResponseSchema>; +export type SavedQueryResponseSchema = ResponseSchema>; +export type ThresholdResponseSchema = ResponseSchema>; +export type MachineLearningResponseSchema = ResponseSchema< + t.TypeOf +>; +export type NewTermsResponseSchema = ResponseSchema>; + export interface RulePreviewLogs { errors: string[]; warnings: string[]; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts index e12fbf2918302..5c934b0d2e040 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts @@ -12,5 +12,3 @@ export * from './import_rules_schema'; export * from './prepackaged_rules_schema'; export * from './prepackaged_rules_status_schema'; export * from './rules_bulk_schema'; -export * from './rules_schema'; -export * from './type_timeline_only_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts index 00800b9474716..69e31522ef40a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts @@ -10,12 +10,12 @@ import { pipe } from 'fp-ts/lib/pipeable'; import type { RulesBulkSchema } from './rules_bulk_schema'; import { rulesBulkSchema } from './rules_bulk_schema'; -import type { RulesSchema } from './rules_schema'; import type { ErrorSchema } from './error_schema'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getRulesSchemaMock } from './rules_schema.mocks'; import { getErrorSchemaMock } from './error_schema.mocks'; +import type { FullResponseSchema } from '../request'; describe('prepackaged_rule_schema', () => { test('it should validate a regular message and and error together with a uuid', () => { @@ -73,15 +73,14 @@ describe('prepackaged_rule_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "error"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "error"' + ); expect(message.schema).toEqual({}); }); test('it should NOT validate a type of "query" when it has extra data', () => { - const rule: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const rule: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); rule.invalid_extra_data = 'invalid_extra_data'; const payload: RulesBulkSchema = [rule]; const decoded = rulesBulkSchema.decode(payload); @@ -93,7 +92,7 @@ describe('prepackaged_rule_schema', () => { }); test('it should NOT validate a type of "query" when it has extra data next to a valid error', () => { - const rule: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const rule: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); rule.invalid_extra_data = 'invalid_extra_data'; const payload: RulesBulkSchema = [getErrorSchemaMock(), rule]; const decoded = rulesBulkSchema.decode(payload); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts index 57d812645ed38..65c55f356c44b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts @@ -7,8 +7,8 @@ import * as t from 'io-ts'; -import { rulesSchema } from './rules_schema'; +import { fullResponseSchema } from '../request'; import { errorSchema } from './error_schema'; -export const rulesBulkSchema = t.array(t.union([rulesSchema, errorSchema])); +export const rulesBulkSchema = t.array(t.union([fullResponseSchema, errorSchema])); export type RulesBulkSchema = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index c3fbec8a6d7b3..bf6583a6855f0 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -6,13 +6,19 @@ */ import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../constants'; +import type { + EqlResponseSchema, + MachineLearningResponseSchema, + QueryResponseSchema, + SavedQueryResponseSchema, + SharedResponseSchema, + ThreatMatchResponseSchema, +} from '../request'; import { getListArrayMock } from '../types/lists.mock'; -import type { RulesSchema } from './rules_schema'; - export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; -export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => ({ +const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE): SharedResponseSchema => ({ author: [], id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', created_at: new Date(anchorDate).toISOString(), @@ -24,45 +30,83 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchem from: 'now-6m', immutable: false, name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', references: ['test 1', 'test 2'], - severity: 'high', + severity: 'high' as const, severity_mapping: [], updated_by: 'elastic_kibana', tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', - type: 'query', threat: [], version: 1, output_index: '.siem-signals-default', max_signals: 100, risk_score: 55, risk_score_mapping: [], - language: 'kuery', rule_id: 'query-rule-id', interval: '5m', exceptions_list: getListArrayMock(), related_integrations: [], required_fields: [], setup: '', + throttle: 'no_actions', + actions: [], + building_block_type: undefined, + note: undefined, + license: undefined, + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + timeline_id: undefined, + timeline_title: undefined, + meta: undefined, + rule_name_override: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, }); -export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => { - const basePayload = getRulesSchemaMock(anchorDate); - const { filters, index, query, language, ...rest } = basePayload; +export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): QueryResponseSchema => ({ + ...getResponseBaseParams(anchorDate), + query: 'user.name: root or user.name: admin', + type: 'query', + language: 'kuery', + index: undefined, + data_view_id: undefined, + filters: undefined, + saved_id: undefined, +}); +export const getSavedQuerySchemaMock = ( + anchorDate: string = ANCHOR_DATE +): SavedQueryResponseSchema => ({ + ...getResponseBaseParams(anchorDate), + query: 'user.name: root or user.name: admin', + type: 'saved_query', + saved_id: 'save id 123', + language: 'kuery', + index: undefined, + data_view_id: undefined, + filters: undefined, +}); +export const getRulesMlSchemaMock = ( + anchorDate: string = ANCHOR_DATE +): MachineLearningResponseSchema => { return { - ...rest, + ...getResponseBaseParams(anchorDate), type: 'machine_learning', anomaly_threshold: 59, machine_learning_job_id: 'some_machine_learning_job_id', }; }; -export const getThreatMatchingSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => { +export const getThreatMatchingSchemaMock = ( + anchorDate: string = ANCHOR_DATE +): ThreatMatchResponseSchema => { return { - ...getRulesSchemaMock(anchorDate), + ...getResponseBaseParams(anchorDate), type: 'threat_match', + query: 'user.name: root or user.name: admin', + language: 'kuery', threat_index: ['index-123'], threat_mapping: [{ entries: [{ field: 'host.name', type: 'mapping', value: 'host.name' }] }], threat_query: '*:*', @@ -84,6 +128,14 @@ export const getThreatMatchingSchemaMock = (anchorDate: string = ANCHOR_DATE): R }, }, ], + index: undefined, + data_view_id: undefined, + filters: undefined, + saved_id: undefined, + threat_indicator_path: undefined, + threat_language: undefined, + concurrent_searches: undefined, + items_per_search: undefined, }; }; @@ -91,7 +143,9 @@ export const getThreatMatchingSchemaMock = (anchorDate: string = ANCHOR_DATE): R * Useful for e2e backend tests where it doesn't have date time and other * server side properties attached to it. */ -export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial => { +export const getThreatMatchingSchemaPartialMock = ( + enabled = false +): Partial => { return { author: [], created_by: 'elastic', @@ -160,11 +214,17 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial { +export const getRulesEqlSchemaMock = (anchorDate: string = ANCHOR_DATE): EqlResponseSchema => { return { - ...getRulesSchemaMock(anchorDate), + ...getResponseBaseParams(anchorDate), language: 'eql', type: 'eql', query: 'process where true', + index: undefined, + data_view_id: undefined, + filters: undefined, + timestamp_field: undefined, + event_category_override: undefined, + tiebreaker_field: undefined, }; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts index bac55c8510929..0a337eb28bc1c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts @@ -7,35 +7,23 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import type * as t from 'io-ts'; -import type { RulesSchema } from './rules_schema'; -import { - rulesSchema, - checkTypeDependents, - getDependents, - addSavedId, - addQueryFields, - addTimelineTitle, - addMlFields, - addThreatMatchFields, - addEqlFields, -} from './rules_schema'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import type { TypeAndTimelineOnly } from './type_timeline_only_schema'; import { getRulesSchemaMock, getRulesMlSchemaMock, + getSavedQuerySchemaMock, getThreatMatchingSchemaMock, getRulesEqlSchemaMock, } from './rules_schema.mocks'; -import type { ListArray } from '@kbn/securitysolution-io-ts-list-types'; +import { fullResponseSchema } from '../request'; +import type { FullResponseSchema } from '../request'; describe('rules_schema', () => { test('it should validate a type of "query" without anything extra', () => { const payload = getRulesSchemaMock(); - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getRulesSchemaMock(); @@ -45,10 +33,10 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const payload: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); payload.invalid_extra_data = 'invalid_extra_data'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -57,55 +45,48 @@ describe('rules_schema', () => { }); test('it should NOT validate invalid_data for the type', () => { - const payload: Omit & { type: string } = getRulesSchemaMock(); + const payload: Omit & { type: string } = getRulesSchemaMock(); payload.type = 'invalid_data'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "invalid_data" supplied to "type"', - ]); + expect(getPaths(left(message.errors))).toHaveLength(1); expect(message.schema).toEqual({}); }); - test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getRulesSchemaMock(); + test('it should validate a type of "query" with a saved_id together', () => { + const payload: FullResponseSchema & { saved_id?: string } = getRulesSchemaMock(); payload.type = 'query'; payload.saved_id = 'save id 123'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); - expect(message.schema).toEqual({}); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); }); test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; + const payload = getSavedQuerySchemaMock(); - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - - expected.type = 'saved_query'; - expected.saved_id = 'save id 123'; + const expected = getSavedQuerySchemaMock(); expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(expected); }); test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; + const payload: FullResponseSchema & { saved_id?: string } = getSavedQuerySchemaMock(); + // @ts-expect-error delete payload.saved_id; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -116,12 +97,11 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "saved_query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; + const payload: FullResponseSchema & { saved_id?: string; invalid_extra_data?: string } = + getSavedQuerySchemaMock(); payload.invalid_extra_data = 'invalid_extra_data'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -134,7 +114,7 @@ describe('rules_schema', () => { payload.timeline_id = 'some timeline id'; payload.timeline_title = 'some timeline title'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getRulesSchemaMock(); @@ -146,12 +126,12 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const payload: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); payload.timeline_id = 'some timeline id'; payload.timeline_title = 'some timeline title'; payload.invalid_extra_data = 'invalid_extra_data'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -159,575 +139,11 @@ describe('rules_schema', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_title = 'some timeline title'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_title = 'some timeline title'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_id = 'some timeline id'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - describe('checkTypeDependents', () => { - test('it should validate a type of "query" without anything extra', () => { - const payload = getRulesSchemaMock(); - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate invalid_data for the type', () => { - const payload: Omit & { type: string } = getRulesSchemaMock(); - payload.type = 'invalid_data'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "invalid_data" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getRulesSchemaMock(); - payload.type = 'query'; - payload.saved_id = 'save id 123'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - - expected.type = 'saved_query'; - expected.saved_id = 'save id 123'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; - delete payload.saved_id; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "saved_id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - expected.timeline_id = 'some timeline id'; - expected.timeline_title = 'some timeline title'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_title = 'some timeline title'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_title = 'some timeline title'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_id = 'some timeline id'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('getDependents', () => { - test('it should validate a type of "query" without anything extra', () => { - const payload = getRulesSchemaMock(); - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should validate a namespace as string', () => { - const payload = { - ...getRulesSchemaMock(), - namespace: 'a namespace', - }; - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should NOT validate invalid_data for the type', () => { - const payload: Omit & { type: string } = getRulesSchemaMock(); - payload.type = 'invalid_data'; - - const dependents = getDependents(payload as unknown as TypeAndTimelineOnly); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "invalid_data" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getRulesSchemaMock(); - payload.type = 'query'; - payload.saved_id = 'save id 123'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - - expected.type = 'saved_query'; - expected.saved_id = 'save id 123'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; - delete payload.saved_id; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "saved_id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - expected.timeline_id = 'some timeline id'; - expected.timeline_title = 'some timeline title'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_title = 'some timeline title'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_title = 'some timeline title'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_id = 'some timeline id'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it validates an ML rule response', () => { - const payload = getRulesMlSchemaMock(); - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesMlSchemaMock(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it rejects a response with both ML and query properties', () => { - const payload = { - ...getRulesSchemaMock(), - ...getRulesMlSchemaMock(), - }; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "query,language"']); - expect(message.schema).toEqual({}); - }); - - test('it validates a threat_match response', () => { - const payload = getThreatMatchingSchemaMock(); - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getThreatMatchingSchemaMock(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it rejects a response with threat_match properties but type of "query"', () => { - const payload: RulesSchema = { - ...getThreatMatchingSchemaMock(), - type: 'query', - }; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'invalid keys "threat_index,["index-123"],threat_mapping,[{"entries":[{"field":"host.name","type":"mapping","value":"host.name"}]}],threat_query,threat_filters,[{"bool":{"must":[{"query_string":{"query":"host.name: linux","analyze_wildcard":true,"time_zone":"Zulu"}}],"filter":[],"should":[],"must_not":[]}}]"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it validates an eql rule response', () => { - const payload = getRulesEqlSchemaMock(); - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesEqlSchemaMock(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - }); - - describe('addSavedId', () => { - test('should return empty array if not given a type of "saved_query"', () => { - const emptyArray = addSavedId({ type: 'query' }); - const expected: t.Mixed[] = []; - expect(emptyArray).toEqual(expected); - }); - - test('should array of size 2 given a "saved_query"', () => { - const array = addSavedId({ type: 'saved_query' }); - expect(array.length).toEqual(2); - }); - }); - - describe('addTimelineTitle', () => { - test('should return empty array if not given a timeline_id', () => { - const emptyArray = addTimelineTitle({ type: 'query' }); - const expected: t.Mixed[] = []; - expect(emptyArray).toEqual(expected); - }); - - test('should array of size 2 given a "timeline_id" that is not null', () => { - const array = addTimelineTitle({ type: 'query', timeline_id: 'some id' }); - expect(array.length).toEqual(2); - }); - }); - - describe('addQueryFields', () => { - test('should return empty array if type is not "query"', () => { - const fields = addQueryFields({ type: 'machine_learning' }); - const expected: t.Mixed[] = []; - expect(fields).toEqual(expected); - }); - - test('should return two fields for a rule of type "query"', () => { - const fields = addQueryFields({ type: 'query' }); - expect(fields.length).toEqual(3); - }); - - test('should return two fields for a rule of type "threshold"', () => { - const fields = addQueryFields({ type: 'threshold' }); - expect(fields.length).toEqual(3); - }); - - test('should return two fields for a rule of type "saved_query"', () => { - const fields = addQueryFields({ type: 'saved_query' }); - expect(fields.length).toEqual(3); - }); - - test('should return two fields for a rule of type "threat_match"', () => { - const fields = addQueryFields({ type: 'threat_match' }); - expect(fields.length).toEqual(3); - }); - }); - - describe('addMlFields', () => { - test('should return empty array if type is not "machine_learning"', () => { - const fields = addMlFields({ type: 'query' }); - const expected: t.Mixed[] = []; - expect(fields).toEqual(expected); - }); - - test('should return two fields for a rule of type "machine_learning"', () => { - const fields = addMlFields({ type: 'machine_learning' }); - expect(fields.length).toEqual(2); - }); - }); - describe('exceptions_list', () => { test('it should validate an empty array for "exceptions_list"', () => { const payload = getRulesSchemaMock(); payload.exceptions_list = []; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getRulesSchemaMock(); @@ -737,11 +153,11 @@ describe('rules_schema', () => { }); test('it should NOT validate when "exceptions_list" is not expected type', () => { - const payload: Omit & { + const payload: Omit & { exceptions_list?: string; } = { ...getRulesSchemaMock(), exceptions_list: 'invalid_data' }; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -750,53 +166,13 @@ describe('rules_schema', () => { ]); expect(message.schema).toEqual({}); }); - - test('it should default to empty array if "exceptions_list" is undefined ', () => { - const payload: Omit & { - exceptions_list?: ListArray; - } = getRulesSchemaMock(); - payload.exceptions_list = undefined; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ ...payload, exceptions_list: [] }); - }); - }); - - describe('addThreatMatchFields', () => { - test('should return empty array if type is not "threat_match"', () => { - const fields = addThreatMatchFields({ type: 'query' }); - const expected: t.Mixed[] = []; - expect(fields).toEqual(expected); - }); - - test('should return nine (9) fields for a rule of type "threat_match"', () => { - const fields = addThreatMatchFields({ type: 'threat_match' }); - expect(fields.length).toEqual(10); - }); - }); - - describe('addEqlFields', () => { - test('should return empty array if type is not "eql"', () => { - const fields = addEqlFields({ type: 'query' }); - const expected: t.Mixed[] = []; - expect(fields).toEqual(expected); - }); - - test('should return 3 fields for a rule of type "eql"', () => { - const fields = addEqlFields({ type: 'eql' }); - expect(fields.length).toEqual(6); - }); }); describe('data_view_id', () => { test('it should validate a type of "query" with "data_view_id" defined', () => { const payload = { ...getRulesSchemaMock(), data_view_id: 'logs-*' }; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getRulesSchemaMock(), data_view_id: 'logs-*' }; @@ -806,18 +182,16 @@ describe('rules_schema', () => { }); test('it should validate a type of "saved_query" with "data_view_id" defined', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; + const payload: FullResponseSchema & { saved_id?: string; data_view_id?: string } = + getSavedQuerySchemaMock(); payload.data_view_id = 'logs-*'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); + const expected: FullResponseSchema & { saved_id?: string; data_view_id?: string } = + getSavedQuerySchemaMock(); - expected.type = 'saved_query'; - expected.saved_id = 'save id 123'; expected.data_view_id = 'logs-*'; expect(getPaths(left(message.errors))).toEqual([]); @@ -827,8 +201,7 @@ describe('rules_schema', () => { test('it should validate a type of "eql" with "data_view_id" defined', () => { const payload = { ...getRulesEqlSchemaMock(), data_view_id: 'logs-*' }; - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getRulesEqlSchemaMock(), data_view_id: 'logs-*' }; @@ -840,8 +213,7 @@ describe('rules_schema', () => { test('it should validate a type of "threat_match" with "data_view_id" defined', () => { const payload = { ...getThreatMatchingSchemaMock(), data_view_id: 'logs-*' }; - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getThreatMatchingSchemaMock(), data_view_id: 'logs-*' }; @@ -853,8 +225,7 @@ describe('rules_schema', () => { test('it should NOT validate a type of "machine_learning" with "data_view_id" defined', () => { const payload = { ...getRulesMlSchemaMock(), data_view_id: 'logs-*' }; - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts deleted file mode 100644 index 794ef71bf0536..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts +++ /dev/null @@ -1,366 +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 * as t from 'io-ts'; -import { isObject } from 'lodash/fp'; -import type { Either } from 'fp-ts/lib/Either'; -import { left, fold } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import { - actions, - from, - machine_learning_job_id, - risk_score, - DefaultRiskScoreMappingArray, - DefaultSeverityMappingArray, - threat_index, - concurrent_searches, - items_per_search, - threat_query, - threat_filters, - threat_mapping, - threat_language, - threat_indicator_path, - threats, - type, - language, - severity, - throttle, - max_signals, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import { DefaultStringArray, version } from '@kbn/securitysolution-io-ts-types'; -import { DefaultListArray } from '@kbn/securitysolution-io-ts-list-types'; - -import { isMlRule } from '../../../machine_learning/helpers'; -import { isThresholdRule } from '../../utils'; -import { RuleExecutionSummary } from '../../rule_monitoring'; -import { - anomaly_threshold, - data_view_id, - description, - enabled, - timestamp_field, - event_category_override, - tiebreaker_field, - false_positives, - id, - immutable, - index, - interval, - rule_id, - name, - output_index, - query, - references, - updated_by, - tags, - to, - created_at, - created_by, - updated_at, - saved_id, - timeline_id, - timeline_title, - threshold, - filters, - meta, - outcome, - alias_target_id, - alias_purpose, - note, - building_block_type, - license, - rule_name_override, - timestamp_override, - namespace, - RelatedIntegrationArray, - RequiredFieldArray, - SetupGuide, -} from '../common'; - -import type { TypeAndTimelineOnly } from './type_timeline_only_schema'; -import { typeAndTimelineOnlySchema } from './type_timeline_only_schema'; - -/** - * This is the required fields for the rules schema response. Put all required properties on - * this base for schemas such as create_rules, update_rules, for the correct validation of the - * output schema. - */ -export const requiredRulesSchema = t.type({ - author: DefaultStringArray, - description, - enabled, - false_positives, - from, - id, - immutable, - interval, - rule_id, - output_index, - max_signals, - risk_score, - risk_score_mapping: DefaultRiskScoreMappingArray, - name, - references, - severity, - severity_mapping: DefaultSeverityMappingArray, - updated_by, - tags, - to, - type, - threat: threats, - created_at, - updated_at, - created_by, - version, - exceptions_list: DefaultListArray, - related_integrations: RelatedIntegrationArray, - required_fields: RequiredFieldArray, - setup: SetupGuide, -}); - -export type RequiredRulesSchema = t.TypeOf; - -/** - * If you have type dependents or exclusive or situations add them here AND update the - * check_type_dependents file for whichever REST flow it is going through. - */ -export const dependentRulesSchema = t.partial({ - // All but ML - data_view_id, - - // query fields - language, - query, - - // eql only fields - timestamp_field, - event_category_override, - tiebreaker_field, - - // when type = saved_query, saved_id is required - saved_id, - - // These two are required together or not at all. - timeline_id, - timeline_title, - - // ML fields - anomaly_threshold, - machine_learning_job_id, - - // Threshold fields - threshold, - - // Threat Match fields - threat_filters, - threat_index, - threat_query, - concurrent_searches, - items_per_search, - threat_mapping, - threat_language, - threat_indicator_path, -}); - -/** - * This is the partial or optional fields for the rules schema. Put all optional - * properties on this. DO NOT PUT type dependents such as xor relationships here. - * Instead use dependentRulesSchema and check_type_dependents for how to do those. - */ -export const partialRulesSchema = t.partial({ - actions, - building_block_type, - license, - throttle, - rule_name_override, - timestamp_override, - filters, - meta, - outcome, - alias_target_id, - alias_purpose, - index, - namespace, - note, - uuid: id, // Move to 'required' post-migration - execution_summary: RuleExecutionSummary, -}); - -/** - * This is the rules schema WITHOUT typeDependents. You don't normally want to use this for a decode - */ -export const rulesWithoutTypeDependentsSchema = t.intersection([ - t.exact(dependentRulesSchema), - t.exact(partialRulesSchema), - t.exact(requiredRulesSchema), -]); -export type RulesWithoutTypeDependentsSchema = t.TypeOf; - -/** - * This is the rulesSchema you want to use for checking type dependents and all the properties - * through: rulesSchema.decode(someJSONObject) - */ -export const rulesSchema = new t.Type< - RulesWithoutTypeDependentsSchema, - RulesWithoutTypeDependentsSchema, - unknown ->( - 'RulesSchema', - (input: unknown): input is RulesWithoutTypeDependentsSchema => isObject(input), - (input): Either => { - return checkTypeDependents(input); - }, - t.identity -); - -/** - * This is the correct type you want to use for Rules that are outputted from the - * REST interface. This has all base and all optional properties merged together. - */ -export type RulesSchema = t.TypeOf; - -export const addSavedId = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.type === 'saved_query') { - return [ - t.exact(t.type({ saved_id: dependentRulesSchema.props.saved_id })), - t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), - ]; - } else { - return []; - } -}; - -export const addTimelineTitle = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.timeline_id != null) { - return [ - t.exact(t.type({ timeline_title: dependentRulesSchema.props.timeline_title })), - t.exact(t.type({ timeline_id: dependentRulesSchema.props.timeline_id })), - ]; - } else { - return []; - } -}; - -export const addQueryFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (['query', 'saved_query', 'threshold', 'threat_match'].includes(typeAndTimelineOnly.type)) { - return [ - t.exact(t.type({ query: dependentRulesSchema.props.query })), - t.exact(t.type({ language: dependentRulesSchema.props.language })), - t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), - ]; - } else { - return []; - } -}; - -export const addMlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (isMlRule(typeAndTimelineOnly.type)) { - return [ - t.exact(t.type({ anomaly_threshold: dependentRulesSchema.props.anomaly_threshold })), - t.exact( - t.type({ machine_learning_job_id: dependentRulesSchema.props.machine_learning_job_id }) - ), - ]; - } else { - return []; - } -}; - -export const addThresholdFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (isThresholdRule(typeAndTimelineOnly.type)) { - return [ - t.exact(t.type({ threshold: dependentRulesSchema.props.threshold })), - t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })), - t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), - ]; - } else { - return []; - } -}; - -export const addEqlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.type === 'eql') { - return [ - t.exact(t.partial({ timestamp_field: dependentRulesSchema.props.timestamp_field })), - t.exact( - t.partial({ event_category_override: dependentRulesSchema.props.event_category_override }) - ), - t.exact(t.partial({ tiebreaker_field: dependentRulesSchema.props.tiebreaker_field })), - t.exact(t.type({ query: dependentRulesSchema.props.query })), - t.exact(t.type({ language: dependentRulesSchema.props.language })), - t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), - ]; - } else { - return []; - } -}; - -export const addThreatMatchFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.type === 'threat_match') { - return [ - t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), - t.exact(t.type({ threat_query: dependentRulesSchema.props.threat_query })), - t.exact(t.type({ threat_index: dependentRulesSchema.props.threat_index })), - t.exact(t.type({ threat_mapping: dependentRulesSchema.props.threat_mapping })), - t.exact(t.partial({ threat_language: dependentRulesSchema.props.threat_language })), - t.exact(t.partial({ threat_filters: dependentRulesSchema.props.threat_filters })), - t.exact( - t.partial({ threat_indicator_path: dependentRulesSchema.props.threat_indicator_path }) - ), - t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })), - t.exact(t.partial({ concurrent_searches: dependentRulesSchema.props.concurrent_searches })), - t.exact( - t.partial({ - items_per_search: dependentRulesSchema.props.items_per_search, - }) - ), - ]; - } else { - return []; - } -}; - -export const getDependents = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed => { - const dependents: t.Mixed[] = [ - t.exact(requiredRulesSchema), - t.exact(partialRulesSchema), - ...addSavedId(typeAndTimelineOnly), - ...addTimelineTitle(typeAndTimelineOnly), - ...addQueryFields(typeAndTimelineOnly), - ...addMlFields(typeAndTimelineOnly), - ...addThresholdFields(typeAndTimelineOnly), - ...addEqlFields(typeAndTimelineOnly), - ...addThreatMatchFields(typeAndTimelineOnly), - ]; - - if (dependents.length > 1) { - // This unsafe cast is because t.intersection does not use an array but rather a set of - // tuples and really does not look like they expected us to ever dynamically build up - // intersections, but here we are doing that. Looking at their code, although they limit - // the array elements to 5, it looks like you have N number of intersections - const unsafeCast: [t.Mixed, t.Mixed] = dependents as [t.Mixed, t.Mixed]; - return t.intersection(unsafeCast); - } else { - // We are not allowed to call t.intersection with a single value so we return without - // it here normally. - return dependents[0]; - } -}; - -export const checkTypeDependents = (input: unknown): Either => { - const typeOnlyDecoded = typeAndTimelineOnlySchema.decode(input); - const onLeft = (errors: t.Errors): Either => left(errors); - const onRight = ( - typeAndTimelineOnly: TypeAndTimelineOnly - ): Either => { - const intersections = getDependents(typeAndTimelineOnly); - return intersections.decode(input); - }; - return pipe(typeOnlyDecoded, fold(onLeft, onRight)); -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts deleted file mode 100644 index 8026d99713214..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts +++ /dev/null @@ -1,66 +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 { left } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import type { TypeAndTimelineOnly } from './type_timeline_only_schema'; -import { typeAndTimelineOnlySchema } from './type_timeline_only_schema'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; - -describe('prepackaged_rule_schema', () => { - test('it should validate a a type and timeline_id together', () => { - const payload: TypeAndTimelineOnly = { - type: 'query', - timeline_id: 'some id', - }; - const decoded = typeAndTimelineOnlySchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate just a type without a timeline_id of type query', () => { - const payload: TypeAndTimelineOnly = { - type: 'query', - }; - const decoded = typeAndTimelineOnlySchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate just a type of saved_query', () => { - const payload: TypeAndTimelineOnly = { - type: 'saved_query', - }; - const decoded = typeAndTimelineOnlySchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should NOT validate an invalid type', () => { - const payload: Omit & { type: string } = { - type: 'some other type', - }; - const decoded = typeAndTimelineOnlySchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some other type" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts deleted file mode 100644 index b164ab9b44e4f..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts +++ /dev/null @@ -1,21 +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 * as t from 'io-ts'; - -import { type } from '@kbn/securitysolution-io-ts-alerting-types'; -import { timeline_id } from '../common/schemas'; - -/** - * Special schema type that is only the type and the timeline_id. - * This is used for dependent type checking only. - */ -export const typeAndTimelineOnlySchema = t.intersection([ - t.exact(t.type({ type })), - t.exact(t.partial({ timeline_id })), -]); -export type TypeAndTimelineOnly = t.TypeOf; diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 0a5d5170473fb..6b0051f12bc29 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -5,11 +5,11 @@ * 2.0. */ -import type { RulesSchema } from '../../common/detection_engine/schemas/response'; import { rawRules } from '../../server/lib/detection_engine/rules/prepackaged_rules'; import { getMockThreatData } from '../../public/detections/mitre/mitre_tactics_techniques'; import type { CompleteTimeline } from './timeline'; import { getTimeline, getIndicatorMatchTimelineTemplate } from './timeline'; +import type { FullResponseSchema } from '../../common/detection_engine/schemas/request'; export const totalNumberOfPrebuiltRules = rawRules.length; @@ -488,7 +488,9 @@ export const getEditedRule = (): CustomRule => ({ tags: [...getExistingRule().tags, 'edited'], }); -export const expectedExportedRule = (ruleResponse: Cypress.Response): string => { +export const expectedExportedRule = ( + ruleResponse: Cypress.Response +): string => { const { id, updated_at: updatedAt, @@ -498,14 +500,20 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response name, risk_score: riskScore, severity, - query, tags, timeline_id: timelineId, timeline_title: timelineTitle, } = ruleResponse.body; + let query: string | undefined; + if (ruleResponse.body.type === 'query') { + query = ruleResponse.body.query; + } + // NOTE: Order of the properties in this object matters for the tests to work. - const rule: RulesSchema = { + // TODO: Follow up https://github.com/elastic/kibana/pull/137628 and add an explicit type to this object + // without using Partial + const rule: Partial = { id, updated_at: updatedAt, updated_by: updatedBy, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts index 69aa2a4502bc0..e445e5b935af2 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts @@ -5,10 +5,8 @@ * 2.0. */ -import type { - GetInstalledIntegrationsResponse, - RulesSchema, -} from '../../../../../../common/detection_engine/schemas/response'; +import type { FullResponseSchema } from '../../../../../../common/detection_engine/schemas/request'; +import type { GetInstalledIntegrationsResponse } from '../../../../../../common/detection_engine/schemas/response'; import { getRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; import { savedRuleMock, rulesMock } from '../mock'; @@ -25,14 +23,16 @@ import type { FetchRulesProps, } from '../types'; -export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => +export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => Promise.resolve(getRulesSchemaMock()); -export const createRule = async ({ rule, signal }: CreateRulesProps): Promise => +export const createRule = async ({ rule, signal }: CreateRulesProps): Promise => Promise.resolve(getRulesSchemaMock()); -export const patchRule = async ({ ruleProperties, signal }: PatchRuleProps): Promise => - Promise.resolve(getRulesSchemaMock()); +export const patchRule = async ({ + ruleProperties, + signal, +}: PatchRuleProps): Promise => Promise.resolve(getRulesSchemaMock()); export const getPrePackagedRulesStatus = async ({ signal, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts index f2e78eeee99ef..63754adc5e7c8 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts @@ -25,7 +25,6 @@ import type { PreviewResponse, } from '../../../../../common/detection_engine/schemas/request'; import type { - RulesSchema, GetInstalledIntegrationsResponse, RulesReferencedByExceptionListsSchema, } from '../../../../../common/detection_engine/schemas/response'; @@ -74,8 +73,8 @@ export const createRule = async ({ rule, signal }: CreateRulesProps): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { +export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { method: 'PUT', body: JSON.stringify(rule), signal, @@ -92,8 +91,11 @@ export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { +export const patchRule = async ({ + ruleProperties, + signal, +}: PatchRuleProps): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { method: 'PATCH', body: JSON.stringify(ruleProperties), signal, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts index ccd0eb5c80fe6..f949e927ec095 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts @@ -8,9 +8,9 @@ import { Readable } from 'stream'; import type { HapiReadableStream } from '../../rules/types'; -import type { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; +import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; /** * Given a string, builds a hapi stream as our @@ -34,10 +34,7 @@ export const buildHapiStream = (string: string, filename = 'file.ndjson'): HapiR return stream; }; -export const getOutputRuleAlertForRest = (): Omit< - RulesSchema, - 'machine_learning_job_id' | 'anomaly_threshold' -> => ({ +export const getOutputRuleAlertForRest = (): FullResponseSchema => ({ author: ['Elastic'], actions: [], building_block_type: 'default', @@ -93,4 +90,11 @@ export const getOutputRuleAlertForRest = (): Omit< related_integrations: [], required_fields: [], setup: '', + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, + data_view_id: undefined, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts index c5009138b4078..6e76de5aa420d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -16,7 +16,7 @@ import { readRules } from '../../rules/read_rules'; import { buildSiemResponse } from '../utils'; import { createRulesSchema } from '../../../../../common/detection_engine/schemas/request'; -import { newTransformValidate } from './validate'; +import { transformValidate } from './validate'; import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents'; import { createRules } from '../../rules/create_rules'; import { checkDefaultRuleExceptionListReferences } from './utils/check_for_default_rule_exception_list'; @@ -89,7 +89,7 @@ export const createRulesRoute = ( const ruleExecutionSummary = await ruleExecutionLog.getExecutionSummary(createdRule.id); - const [validated, errors] = newTransformValidate(createdRule, ruleExecutionSummary); + const [validated, errors] = transformValidate(createdRule, ruleExecutionSummary); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts index 2df4cb712ddd2..9dfd5b1efed7c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts @@ -14,7 +14,6 @@ import pMap from 'p-map'; import type { PartialRule, FindResult } from '@kbn/alerting-plugin/server'; import type { ActionsClient, FindActionResult } from '@kbn/actions-plugin/server'; import type { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; -import type { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; import type { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; import type { CreateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; import type { RuleAlertType } from '../../rules/types'; @@ -26,6 +25,7 @@ import type { RuleParams } from '../../schemas/rule_schemas'; // eslint-disable-next-line no-restricted-imports import type { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; import type { RuleExecutionSummariesByRuleId } from '../../rule_monitoring'; +import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; type PromiseFromStreams = ImportRulesSchema | Error; const MAX_CONCURRENT_SEARCHES = 10; @@ -92,7 +92,7 @@ export const getIdBulkError = ({ export const transformAlertsToRules = ( rules: RuleAlertType[], legacyRuleActions: Record -): Array> => { +): FullResponseSchema[] => { return rules.map((rule) => internalRuleToAPIResponse(rule, null, legacyRuleActions[rule.id])); }; @@ -104,7 +104,7 @@ export const transformFindAlerts = ( page: number; perPage: number; total: number; - data: Array>; + data: Array>; } | null => { return { page: ruleFindResults.page, @@ -121,7 +121,7 @@ export const transform = ( rule: PartialRule, ruleExecutionSummary?: RuleExecutionSummary | null, legacyRuleActions?: LegacyRulesActionsSavedObject | null -): Partial | null => { +): FullResponseSchema | null => { if (isAlertType(rule)) { return internalRuleToAPIResponse(rule, ruleExecutionSummary, legacyRuleActions); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts index 21db7e52e4f8d..84f693a529a68 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts @@ -7,14 +7,14 @@ import { transformValidate, transformValidateBulkError } from './validate'; import type { BulkError } from '../utils'; -import type { RulesSchema } from '../../../../../common/detection_engine/schemas/response'; import { getRuleMock } from '../__mocks__/request_responses'; import { ruleExecutionSummaryMock } from '../../../../../common/detection_engine/rule_monitoring/mocks'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; -export const ruleOutput = (): RulesSchema => ({ +export const ruleOutput = (): FullResponseSchema => ({ actions: [], author: ['Elastic'], building_block_type: 'default', @@ -67,6 +67,15 @@ export const ruleOutput = (): RulesSchema => ({ related_integrations: [], required_fields: [], setup: '', + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + rule_name_override: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, + data_view_id: undefined, + saved_id: undefined, }); describe('validate', () => { @@ -114,7 +123,7 @@ describe('validate', () => { const rule = getRuleMock(getQueryRuleParams()); const ruleExecutionSumary = ruleExecutionSummaryMock.getSummarySucceeded(); const validatedOrError = transformValidateBulkError('rule-1', rule, ruleExecutionSumary); - const expected: RulesSchema = { + const expected: FullResponseSchema = { ...ruleOutput(), execution_summary: ruleExecutionSumary, }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts index 4183f217a61fe..42e50db79294a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts @@ -11,8 +11,6 @@ import type { PartialRule } from '@kbn/alerting-plugin/server'; import type { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; import { fullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; -import type { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; -import { rulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; import { isAlertType } from '../../rules/types'; import type { BulkError } from '../utils'; import { createBulkErrorObject } from '../utils'; @@ -26,19 +24,6 @@ export const transformValidate = ( rule: PartialRule, ruleExecutionSummary: RuleExecutionSummary | null, legacyRuleActions?: LegacyRulesActionsSavedObject | null -): [RulesSchema | null, string | null] => { - const transformed = transform(rule, ruleExecutionSummary, legacyRuleActions); - if (transformed == null) { - return [null, 'Internal error transforming']; - } else { - return validateNonExact(transformed, rulesSchema); - } -}; - -export const newTransformValidate = ( - rule: PartialRule, - ruleExecutionSummary: RuleExecutionSummary | null, - legacyRuleActions?: LegacyRulesActionsSavedObject | null ): [FullResponseSchema | null, string | null] => { const transformed = transform(rule, ruleExecutionSummary, legacyRuleActions); if (transformed == null) { @@ -52,10 +37,10 @@ export const transformValidateBulkError = ( ruleId: string, rule: PartialRule, ruleExecutionSummary: RuleExecutionSummary | null -): RulesSchema | BulkError => { +): FullResponseSchema | BulkError => { if (isAlertType(rule)) { const transformed = internalRuleToAPIResponse(rule, ruleExecutionSummary); - const [validated, errors] = validateNonExact(transformed, rulesSchema); + const [validated, errors] = validateNonExact(transformed, fullResponseSchema); if (errors != null || validated == null) { return createBulkErrorObject({ ruleId, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts index 31bdfb398c18a..4fea86a2e3395 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -213,6 +213,13 @@ describe('get_export_by_object_ids', () => { version: 1, exceptions_list: getListArrayMock(), execution_summary: undefined, + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, + data_view_id: undefined, }, ], }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts index d5008c87f3b6d..e044c8fdfd1ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts @@ -11,7 +11,6 @@ import { transformDataToNdjson } from '@kbn/securitysolution-utils'; import type { Logger } from '@kbn/core/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { RulesClient, RuleExecutorServices } from '@kbn/alerting-plugin/server'; -import type { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; @@ -22,10 +21,11 @@ import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; // eslint-disable-next-line no-restricted-imports import { legacyGetBulkRuleActionsSavedObject } from '../rule_actions/legacy_get_bulk_rule_actions_saved_object'; import { internalRuleToAPIResponse } from '../schemas/rule_converters'; +import type { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; interface ExportSuccessRule { statusCode: 200; - rule: Partial; + rule: FullResponseSchema; } interface ExportFailedRule { @@ -36,7 +36,7 @@ interface ExportFailedRule { export interface RulesErrors { exportedCount: number; missingRules: Array<{ rule_id: string }>; - rules: Array>; + rules: FullResponseSchema[]; } export const getExportByObjectIds = async ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts index 30be45f5eb163..204d78f5fe7d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts @@ -6,12 +6,12 @@ */ import type { ExportExceptionDetails } from '@kbn/securitysolution-io-ts-list-types'; +import type { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; import type { ExportRulesDetails } from '../../../../common/detection_engine/schemas/response/export_rules_details_schema'; -import type { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; export const getExportDetailsNdjson = ( - rules: Array>, + rules: FullResponseSchema[], missingRules: Array<{ rule_id: string }> = [], exceptionDetails?: ExportExceptionDetails ): string => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 6c7d5d581ce61..d3cdcd82b4989 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -497,6 +497,25 @@ export const sampleSignalHit = (): SignalHit => ({ related_integrations: [], required_fields: [], setup: '', + throttle: 'no_actions', + actions: [], + building_block_type: undefined, + note: undefined, + license: undefined, + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + timeline_id: undefined, + timeline_title: undefined, + meta: undefined, + rule_name_override: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, + index: undefined, + data_view_id: undefined, + filters: undefined, + saved_id: undefined, }, depth: 1, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index db88284bc8881..5609eed4c0801 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -19,7 +19,6 @@ import type { ListClient } from '@kbn/lists-plugin/server'; import type { EcsFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/ecs_field_map'; import type { TypeOfFieldMap } from '@kbn/rule-registry-plugin/common/field_map'; import type { Status } from '../../../../common/detection_engine/schemas/common/schemas'; -import type { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import type { BaseHit, RuleAlertAction, @@ -42,6 +41,7 @@ import type { WrappedFieldsLatest, } from '../../../../common/detection_engine/schemas/alerts'; import type { IRuleExecutionLogForExecutors } from '../rule_monitoring'; +import type { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; export interface ThresholdResult { terms?: Array<{ @@ -192,7 +192,7 @@ export interface Signal { _meta?: { version: number; }; - rule: RulesSchema; + rule: FullResponseSchema; /** * @deprecated Use "parents" instead of "parent" */ diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index 4d4bda5e6b4e0..c7e49ac77e7be 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -18,7 +18,7 @@ import { BulkAction, BulkActionEditType, } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response'; +import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { binaryToString, @@ -375,7 +375,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(rulesResponse.total).to.eql(2); - rulesResponse.data.forEach((rule: RulesSchema) => { + rulesResponse.data.forEach((rule: FullResponseSchema) => { expect(rule.actions).to.eql([ { action_type_id: '.slack', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts index 49c1ba045d5e6..1b9dcb5da28fd 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts @@ -82,6 +82,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const outputRule = getSimpleMlRuleOutput(); + // @ts-expect-error type narrowing is lost due to Omit<> outputRule.machine_learning_job_id = ['legacy_job_id']; outputRule.version = 2; const bodyToCompare = removeServerGeneratedProperties(body); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts index 19447dec2b4a8..dc7209b9f1c98 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts @@ -19,8 +19,6 @@ import { deleteSignalsIndex, getSimpleRuleOutput, removeServerGeneratedProperties, - getSimpleRuleOutputWithoutRuleId, - removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleUpdate, createRule, getSimpleRule, @@ -282,16 +280,16 @@ export default ({ getService }: FtrProviderContext) => { .send([updatedRule1, updatedRule2]) .expect(200); - const outputRule1 = getSimpleRuleOutputWithoutRuleId('rule-1'); + const outputRule1 = getSimpleRuleOutput('rule-1'); outputRule1.name = 'some other name'; outputRule1.version = 2; - const outputRule2 = getSimpleRuleOutputWithoutRuleId('rule-2'); + const outputRule2 = getSimpleRuleOutput('rule-2'); outputRule2.name = 'some other name'; outputRule2.version = 2; - const bodyToCompare1 = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - const bodyToCompare2 = removeServerGeneratedPropertiesIncludingRuleId(body[1]); + const bodyToCompare1 = removeServerGeneratedProperties(body[0]); + const bodyToCompare2 = removeServerGeneratedProperties(body[1]); expect(bodyToCompare1).to.eql(outputRule1); expect(bodyToCompare2).to.eql(outputRule2); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts index f44e72f5cd50a..647c4dddb2bb1 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts @@ -10,7 +10,7 @@ import expect from '@kbn/expect'; import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; -import { +import type { CreateRulesSchema, EqlCreateSchema, QueryCreateSchema, @@ -18,7 +18,6 @@ import { ThresholdCreateSchema, } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; -import { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response'; import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; @@ -106,7 +105,7 @@ export default ({ getService }: FtrProviderContext) => { }; const rule = await createRule(supertest, log, ruleWithException); - const expected: Partial = { + const expected = { ...getSimpleRuleOutput(), exceptions_list: [ { @@ -147,7 +146,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForRuleSuccessOrStatus(supertest, log, rule.id); const bodyToCompare = removeServerGeneratedProperties(rule); - const expected: Partial = { + const expected = { ...getSimpleRuleOutput(), enabled: true, exceptions_list: [ diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts index 1db5c784660fc..4f5cfdcd3ba56 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; +import type { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; /** * This will return a complex rule with all the outputs possible * @param ruleId The ruleId to set which is optional and defaults to rule-1 */ -export const getComplexRule = (ruleId = 'rule-1'): Partial => ({ +export const getComplexRule = (ruleId = 'rule-1'): CreateRulesSchema => ({ actions: [], author: [], name: 'Complex Rule Query', @@ -92,4 +92,6 @@ export const getComplexRule = (ruleId = 'rule-1'): Partial => ({ note: '# some investigation documentation', version: 1, query: 'user.name: root or user.name: admin', + throttle: 'no_actions', + exceptions_list: [], }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts index cc33c2ebff447..1491829b33999 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts @@ -5,13 +5,15 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; +import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +// TODO: Follow up https://github.com/elastic/kibana/pull/137628 and add an explicit type to this object +// without using Partial /** * This will return a complex rule with all the outputs possible * @param ruleId The ruleId to set which is optional and defaults to rule-1 */ -export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => ({ +export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => ({ actions: [], author: [], created_by: 'elastic', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule.ts index da28e867bc976..b1036e1f8b682 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_rule.ts @@ -7,7 +7,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; +import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; @@ -21,7 +21,7 @@ export const getRule = async ( supertest: SuperTest.SuperTest, log: ToolingLog, ruleId: string -): Promise => { +): Promise => { const response = await supertest .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleId}`) .set('kbn-xsrf', 'true'); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts index c845c0d343261..56afa355b0482 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts @@ -5,15 +5,13 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; -import { getSimpleRuleOutput } from './get_simple_rule_output'; - -export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): Partial => { - const rule = getSimpleRuleOutput(ruleId); - const { query, language, index, ...rest } = rule; +import type { MachineLearningResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { getMockSharedResponseSchema } from './get_simple_rule_output'; +import { removeServerGeneratedProperties } from './remove_server_generated_properties'; +const getBaseMlRuleOutput = (ruleId = 'rule-1'): MachineLearningResponseSchema => { return { - ...rest, + ...getMockSharedResponseSchema(ruleId), name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', anomaly_threshold: 44, @@ -21,3 +19,7 @@ export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): Partial = type: 'machine_learning', }; }; + +export const getSimpleMlRuleOutput = (ruleId = 'rule-1') => { + return removeServerGeneratedProperties(getBaseMlRuleOutput(ruleId)); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts index 0d6cf9905d4a2..1fe2f2adecc79 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts @@ -5,13 +5,16 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; +import type { + FullResponseSchema, + SharedResponseSchema, +} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { removeServerGeneratedProperties } from './remove_server_generated_properties'; -/** - * This is the typical output of a simple rule that Kibana will output with all the defaults - * except for the server generated properties. Useful for testing end to end tests. - */ -export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false): Partial => ({ +export const getMockSharedResponseSchema = ( + ruleId = 'rule-1', + enabled = false +): SharedResponseSchema => ({ actions: [], author: [], created_by: 'elastic', @@ -20,10 +23,8 @@ export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false): Partial false_positives: [], from: 'now-6m', immutable: false, - index: ['auditbeat-*'], interval: '5m', rule_id: ruleId, - language: 'kuery', output_index: '', max_signals: 100, related_integrations: [], @@ -31,17 +32,50 @@ export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false): Partial risk_score: 1, risk_score_mapping: [], name: 'Simple Rule Query', - query: 'user.name: root or user.name: admin', references: [], setup: '', - severity: 'high', + severity: 'high' as const, severity_mapping: [], updated_by: 'elastic', tags: [], to: 'now', - type: 'query', threat: [], throttle: 'no_actions', exceptions_list: [], version: 1, + id: 'id', + updated_at: '2020-07-08T16:36:32.377Z', + created_at: '2020-07-08T16:36:32.377Z', + building_block_type: undefined, + note: undefined, + license: undefined, + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + timeline_id: undefined, + timeline_title: undefined, + meta: undefined, + rule_name_override: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, }); + +const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): FullResponseSchema => ({ + ...getMockSharedResponseSchema(ruleId, enabled), + index: ['auditbeat-*'], + language: 'kuery', + query: 'user.name: root or user.name: admin', + type: 'query', + data_view_id: undefined, + filters: undefined, + saved_id: undefined, +}); + +/** + * This is the typical output of a simple rule that Kibana will output with all the defaults + * except for the server generated properties. Useful for testing end to end tests. + */ +export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => { + return removeServerGeneratedProperties(getQueryRuleOutput(ruleId, enabled)); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts index 45dd0bfd5d477..c96537bfd0813 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts @@ -5,10 +5,12 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; import { getSimpleRuleOutput } from './get_simple_rule_output'; +import { RuleWithoutServerGeneratedProperties } from './remove_server_generated_properties'; -export const getSimpleRuleOutputWithWebHookAction = (actionId: string): Partial => ({ +export const getSimpleRuleOutputWithWebHookAction = ( + actionId: string +): RuleWithoutServerGeneratedProperties => ({ ...getSimpleRuleOutput(), throttle: 'rule', actions: [ diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts index dbf94965278d6..56b5ab66773bb 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts @@ -5,14 +5,16 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; import { getSimpleRuleOutput } from './get_simple_rule_output'; +import { RuleWithoutServerGeneratedProperties } from './remove_server_generated_properties'; /** * This is the typical output of a simple rule that Kibana will output with all the defaults except * for all the server generated properties such as created_by. Useful for testing end to end tests. */ -export const getSimpleRuleOutputWithoutRuleId = (ruleId = 'rule-1'): Partial => { +export const getSimpleRuleOutputWithoutRuleId = ( + ruleId = 'rule-1' +): Omit => { const rule = getSimpleRuleOutput(ruleId); const { rule_id: rId, ...ruleWithoutRuleId } = rule; return ruleWithoutRuleId; diff --git a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts index 5f863c0e62b9b..8d8a34bba8b79 100644 --- a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts +++ b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts @@ -6,6 +6,15 @@ */ import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { omit, pickBy } from 'lodash'; + +const serverGeneratedProperties = ['id', 'created_at', 'updated_at', 'execution_summary'] as const; + +type ServerGeneratedProperties = typeof serverGeneratedProperties[number]; +export type RuleWithoutServerGeneratedProperties = Omit< + FullResponseSchema, + ServerGeneratedProperties +>; /** * This will remove server generated properties such as date times, etc... @@ -13,14 +22,12 @@ import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/de */ export const removeServerGeneratedProperties = ( rule: FullResponseSchema -): Partial => { - const { - /* eslint-disable @typescript-eslint/naming-convention */ - id, - created_at, - updated_at, - execution_summary, - ...removedProperties - } = rule; - return removedProperties; +): RuleWithoutServerGeneratedProperties => { + const removedProperties = omit(rule, serverGeneratedProperties); + + // We're only removing undefined values, so this cast correctly narrows the type + return pickBy( + removedProperties, + (value) => value !== undefined + ) as RuleWithoutServerGeneratedProperties; }; diff --git a/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts index 468cbdfa23aa5..4f8b24e623ac3 100644 --- a/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts @@ -5,11 +5,9 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; - import { getSimpleRuleOutput } from './get_simple_rule_output'; -export const resolveSimpleRuleOutput = ( - ruleId = 'rule-1', - enabled = false -): Partial => ({ outcome: 'exactMatch', ...getSimpleRuleOutput(ruleId, enabled) }); +export const resolveSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => ({ + ...getSimpleRuleOutput(ruleId, enabled), + outcome: 'exactMatch', +}); From f079550bfdbf7cccc944d86dcd9605e2b7c1bce8 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Tue, 13 Sep 2022 18:11:02 +0200 Subject: [PATCH 23/28] fix: disables notes & pinned tabs for timeline templates (#140478) --- .../timelines/components/timeline/tabs_content/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx index 9b633b0baed7f..407a66086805f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx @@ -362,11 +362,11 @@ const TabsContentComponent: React.FC = ({ data-test-subj={`timelineTabs-${TimelineTabs.notes}`} onClick={setNotesAsActiveTab} isSelected={activeTab === TimelineTabs.notes} - disabled={false} + disabled={timelineType === TimelineType.template} key={TimelineTabs.notes} > {i18n.NOTES_TAB} - {showTimeline && numberOfNotes > 0 && ( + {showTimeline && numberOfNotes > 0 && timelineType === TimelineType.default && (
{numberOfNotes}
@@ -375,11 +375,12 @@ const TabsContentComponent: React.FC = ({ {i18n.PINNED_TAB} - {showTimeline && numberOfPinnedEvents > 0 && ( + {showTimeline && numberOfPinnedEvents > 0 && timelineType === TimelineType.default && (
{numberOfPinnedEvents}
From b0cec96d38478f7dadf9c48317d2b81569c21eee Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 18:20:20 +0200 Subject: [PATCH 24/28] Update dependency cypress to v10 (main) (#133168) --- .github/CODEOWNERS | 38 +- package.json | 16 +- .../__fixtures__/cypress_report.xml | 2 +- .../add_messages_to_report.test.ts | 4 +- x-pack/plugins/apm/ftr_e2e/cypress.config.ts | 36 + x-pack/plugins/apm/ftr_e2e/cypress.json | 19 - .../power_user/feature_flag/comparison.cy.ts} | 0 .../infrastructure/generate_data.ts | 0 .../infrastructure/infrastructure_page.cy.ts} | 0 .../integration_policy.cy.ts} | 0 .../power_user/no_data_screen.cy.ts} | 0 .../power_user/rules/error_count.cy.ts} | 0 .../settings/agent_configurations.cy.ts} | 0 .../power_user/settings/custom_links.cy.ts} | 0 .../read_only_user/deep_links.cy.ts} | 0 .../read_only_user/dependencies.cy.ts} | 0 .../errors/error_details.cy.ts} | 0 .../read_only_user/errors/errors_page.cy.ts} | 0 .../read_only_user/errors/generate_data.ts | 0 .../read_only_user/home.cy.ts} | 4 + .../service_inventory/generate_data.ts | 0 .../header_filters/generate_data.ts | 0 .../header_filters/header_filters.cy.ts} | 0 .../service_inventory.cy.ts} | 0 .../aws_lambda/aws_lamba.cy.ts} | 0 .../aws_lambda/generate_data.ts | 0 .../service_overview/errors_table.cy.ts} | 0 .../service_overview/header_filters.cy.ts} | 0 .../service_overview/instances_table.cy.ts} | 0 .../service_overview/service_overview.cy.ts} | 0 .../service_overview/time_comparison.cy.ts} | 0 .../generate_span_links_data.ts | 0 .../transaction_details/span_links.cy.ts} | 0 .../transaction_details.cy.ts} | 0 .../transactions_overview.cy.ts} | 0 .../read_only_user/tutorial/tutorial.cy.ts} | 0 .../apm/ftr_e2e/cypress/plugins/index.ts | 4 +- .../apm/ftr_e2e/cypress/support/commands.ts | 37 +- .../cypress/support/{index.ts => e2e.ts} | 4 + .../apm/ftr_e2e/cypress_test_runner.ts | 7 +- .../applications/app_search/cypress.config.js | 34 + .../applications/app_search/cypress.json | 20 - .../engines.spec.ts => e2e/engines.cy.ts} | 0 .../cypress.config.js | 35 + .../enterprise_search_overview/cypress.json | 21 - .../overview.spec.ts => e2e/overview.cy.ts} | 0 .../workplace_search/cypress.config.js | 34 + .../workplace_search/cypress.json | 20 - .../overview.spec.ts => e2e/overview.cy.ts} | 0 x-pack/plugins/fleet/cypress.config.ts | 44 ++ x-pack/plugins/fleet/cypress/cypress.json | 24 - .../fleet/cypress/downloads/downloads.html | Bin 4956 -> 0 bytes .../a11y/home_page.cy.ts} | 2 +- .../agent_binary_download_source.cy.ts} | 0 .../agent_list.cy.ts} | 0 .../agent_policy.cy.ts} | 0 .../enrollment_token.cy.ts} | 0 .../fleet_agent_flyout.cy.ts} | 0 .../fleet_settings.cy.ts} | 0 .../fleet_startup.cy.ts} | 0 .../install_assets.cy.ts} | 0 .../integrations_mock.cy.ts} | 0 .../integrations_real.cy.ts} | 0 .../package_policy.cy.ts} | 0 ...vileges_fleet_all_integrations_none.cy.ts} | 0 ...vileges_fleet_all_integrations_read.cy.ts} | 0 ...vileges_fleet_none_integrations_all.cy.ts} | 0 .../cypress/support/{index.ts => e2e.ts} | 0 x-pack/plugins/fleet/cypress/tsconfig.json | 3 +- x-pack/plugins/fleet/package.json | 6 +- x-pack/plugins/fleet/tsconfig.json | 4 +- x-pack/plugins/osquery/cypress.config.ts | 42 ++ x-pack/plugins/osquery/cypress/cypress.json | 22 - .../all/add_integration.cy.ts} | 0 .../alerts.spec.ts => e2e/all/alerts.cy.ts} | 0 .../all/discover.cy.ts} | 0 .../all/edit_saved_queries.cy.ts} | 0 .../all/live_query.cy.ts} | 0 .../metrics.spec.ts => e2e/all/metrics.cy.ts} | 0 .../all/packs.spec.ts => e2e/all/packs.cy.ts} | 0 .../all/saved_queries.cy.ts} | 0 .../admin.spec.ts => e2e/roles/admin.cy.ts} | 0 .../roles/alert_test.cy.ts} | 0 .../reader.spec.ts => e2e/roles/reader.cy.ts} | 0 .../roles/t1_analyst.cy.ts} | 0 .../roles/t2_analyst.cy.ts} | 0 .../osquery/cypress/support/coverage.ts | 4 +- .../cypress/support/{index.ts => e2e.ts} | 0 x-pack/plugins/osquery/cypress/tsconfig.json | 3 +- x-pack/plugins/osquery/package.json | 4 +- x-pack/plugins/osquery/tsconfig.json | 2 + .../security_solution/cypress/README.md | 8 +- .../detection_alerts/alerts_details.cy.ts} | 0 .../event_correlation_rule.cy.ts} | 0 .../cypress/cypress.config.ts | 30 + .../security_solution/cypress/cypress.json | 12 - .../cypress/cypress_ci.config.ts | 34 + .../security_solution/cypress/cypress_ci.json | 16 - .../cases/attach_alert_to_case.cy.ts} | 0 .../cases/attach_timeline.cy.ts} | 0 .../cases/connector_options.cy.ts} | 0 .../cases/connectors.cy.ts} | 0 .../cases/creation.cy.ts} | 0 .../cases/privileges.cy.ts} | 0 .../dashboards/entity_analytics.cy.ts} | 0 .../data_sources/create_runtime_field.cy.ts} | 0 .../data_sources/sourcerer.cy.ts} | 0 .../detection_alerts/alert_flyout.cy.ts} | 0 .../detection_alerts/alerts_details.cy.ts} | 0 ...s_detection_callouts_index_outdated.cy.ts} | 0 .../building_block_alerts.cy.ts} | 0 .../changing_alert_status.cy.ts} | 0 .../detection_alerts/cti_enrichments.cy.ts} | 0 .../investigate_in_timeline.cy.ts} | 0 .../missing_privileges_callout.cy.ts} | 0 .../open_alerts_in_timeline.cy.ts} | 0 .../all_rules_read_only.cy.ts} | 0 .../detection_rules/bulk_edit_rules.cy.ts} | 0 .../detection_rules/custom_query_rule.cy.ts} | 1 + .../custom_query_rule_data_view.cy.ts} | 0 .../event_correlation_rule.cy.ts} | 0 .../detection_rules/export_rule.cy.ts} | 0 .../detection_rules/import_rules.cy.ts} | 0 .../indicator_match_rule.cy.ts} | 0 .../detection_rules/links.cy.ts} | 0 .../machine_learning_rule.cy.ts} | 0 .../detection_rules/new_terms_rule.cy.ts} | 0 .../detection_rules/override.cy.ts} | 0 .../detection_rules/prebuilt_rules.cy.ts} | 0 .../related_integrations.cy.ts} | 0 .../detection_rules/rules_selection.cy.ts} | 0 .../rules_table_auto_refresh.cy.ts} | 0 .../detection_rules/sorting.cy.ts} | 0 .../detection_rules/threshold_rule.cy.ts} | 0 .../alerts_table_flow/add_exception.cy.ts} | 0 .../exceptions/exceptions_flyout.cy.ts} | 0 .../all_exception_lists_read_only.cy.ts} | 0 .../exceptions/exceptions_table.cy.ts} | 0 .../rule_details_flow/add_exception.spec.ts | 0 .../add_exception_data_view.spect.ts | 0 .../rule_details_flow/edit_exception.spec.ts | 0 .../edit_exception_data_view.spec.ts | 0 .../rule_details_flow/read_only_view.spect.ts | 0 .../filters/pinned_filters.cy.ts} | 0 .../guided_onboarding/tour.cy.ts} | 0 .../header/navigation.cy.ts} | 0 .../header/search_bar.cy.ts} | 0 .../host_details/risk_tab.cy.ts} | 0 .../hosts/events_viewer.cy.ts} | 0 .../hosts/host_risk_tab.cy.ts} | 0 .../hosts/hosts_risk_column.cy.ts} | 0 .../hosts/inspect.cy.ts} | 0 .../ml/ml_conditional_links.cy.ts} | 0 .../network/hover_actions.cy.ts} | 0 .../network/inspect.cy.ts} | 0 .../network/overflow_items.cy.ts} | 0 .../overview/cti_link_panel.cy.ts} | 0 .../overview/overview.cy.ts} | 0 .../pagination/pagination.cy.ts} | 0 .../timeline_templates/creation.cy.ts} | 0 .../timeline_templates/export.cy.ts} | 0 .../timelines/creation.cy.ts} | 0 .../timelines/data_providers.cy.ts} | 0 .../timelines/export.cy.ts} | 0 .../timelines/fields_browser.cy.ts} | 0 .../timelines/flyout_button.cy.ts} | 0 .../timelines/full_screen.cy.ts} | 0 .../timelines/inspect.cy.ts} | 0 .../timelines/local_storage.cy.ts} | 0 .../timelines/notes_tab.cy.ts} | 0 .../timelines/open_timeline.cy.ts} | 0 .../timelines/overview.cy.tsx} | 0 .../timelines/pagination.cy.ts} | 0 .../timelines/query_tab.cy.ts} | 0 .../timelines/row_renderers.cy.ts} | 0 .../timelines/search_or_filter.cy.ts} | 0 .../timelines/toggle_column.cy.ts} | 0 .../urls/compatibility.cy.ts} | 0 .../urls/not_found.cy.ts} | 0 .../state.spec.ts => e2e/urls/state.cy.ts} | 0 .../users/inspect.cy.ts} | 0 .../users/user_details.cy.ts} | 0 .../users/users_tabs.cy.ts} | 0 .../value_lists/value_lists.cy.ts} | 0 .../cypress/support/{index.js => e2e.js} | 0 .../detection_rules/custom_query_rule.cy.ts} | 0 .../detection_rules/threshold_rule.cy.ts} | 0 .../threat_hunting/cases/import_case.cy.ts} | 0 .../timeline/import_timeline.cy.ts} | 0 x-pack/plugins/security_solution/package.json | 23 +- .../cypress/cypress.config.ts | 37 ++ .../threat_intelligence/cypress/cypress.json | 20 - .../empty_page.cy.ts} | 0 .../indicators.cy.ts} | 0 .../query_bar.spec.ts => e2e/query_bar.cy.ts} | 0 .../timeline.spec.ts => e2e/timeline.cy.ts} | 0 .../cypress/support/{index.js => e2e.js} | 0 .../plugins/threat_intelligence/package.json | 19 +- .../test/security_solution_cypress/runner.ts | 5 +- .../threat_intelligence_cypress/runner.ts | 5 +- yarn.lock | 621 +++--------------- 201 files changed, 511 insertions(+), 815 deletions(-) create mode 100644 x-pack/plugins/apm/ftr_e2e/cypress.config.ts delete mode 100644 x-pack/plugins/apm/ftr_e2e/cypress.json rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/power_user/feature_flag/comparison.spec.ts => e2e/power_user/feature_flag/comparison.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration => e2e}/power_user/infrastructure/generate_data.ts (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/power_user/infrastructure/infrastructure_page.spec.ts => e2e/power_user/infrastructure/infrastructure_page.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/power_user/integration_settings/integration_policy.spec.ts => e2e/power_user/integration_settings/integration_policy.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/power_user/no_data_screen.ts => e2e/power_user/no_data_screen.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/power_user/rules/error_count.spec.ts => e2e/power_user/rules/error_count.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/power_user/settings/agent_configurations.spec.ts => e2e/power_user/settings/agent_configurations.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/power_user/settings/custom_links.spec.ts => e2e/power_user/settings/custom_links.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/deep_links.spec.ts => e2e/read_only_user/deep_links.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/dependencies.spec.ts => e2e/read_only_user/dependencies.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/errors/error_details.spec.ts => e2e/read_only_user/errors/error_details.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/errors/errors_page.spec.ts => e2e/read_only_user/errors/errors_page.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration => e2e}/read_only_user/errors/generate_data.ts (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/home.spec.ts => e2e/read_only_user/home.cy.ts} (95%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration => e2e}/read_only_user/service_inventory/generate_data.ts (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration => e2e}/read_only_user/service_inventory/header_filters/generate_data.ts (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/service_inventory/header_filters/header_filters.spec.ts => e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/service_inventory/service_inventory.spec.ts => e2e/read_only_user/service_inventory/service_inventory.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/service_overview/aws_lambda/aws_lamba.spec.ts => e2e/read_only_user/service_overview/aws_lambda/aws_lamba.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration => e2e}/read_only_user/service_overview/aws_lambda/generate_data.ts (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/service_overview/errors_table.spec.ts => e2e/read_only_user/service_overview/errors_table.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/service_overview/header_filters.spec.ts => e2e/read_only_user/service_overview/header_filters.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/service_overview/instances_table.spec.ts => e2e/read_only_user/service_overview/instances_table.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/service_overview/service_overview.spec.ts => e2e/read_only_user/service_overview/service_overview.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/service_overview/time_comparison.spec.ts => e2e/read_only_user/service_overview/time_comparison.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration => e2e}/read_only_user/transaction_details/generate_span_links_data.ts (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/transaction_details/span_links.spec.ts => e2e/read_only_user/transaction_details/span_links.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/transaction_details/transaction_details.spec.ts => e2e/read_only_user/transaction_details/transaction_details.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/transactions_overview/transactions_overview.spec.ts => e2e/read_only_user/transactions_overview/transactions_overview.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/{integration/read_only_user/tutorial/tutorial.spec.ts => e2e/read_only_user/tutorial/tutorial.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/cypress/support/{index.ts => e2e.ts} (80%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/cypress.config.js delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/cypress.json rename x-pack/plugins/enterprise_search/public/applications/app_search/cypress/{integration/engines.spec.ts => e2e/engines.cy.ts} (100%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress.config.js delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress.json rename x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress/{integration/overview.spec.ts => e2e/overview.cy.ts} (100%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress.config.js delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress.json rename x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress/{integration/overview.spec.ts => e2e/overview.cy.ts} (100%) create mode 100644 x-pack/plugins/fleet/cypress.config.ts delete mode 100644 x-pack/plugins/fleet/cypress/cypress.json delete mode 100644 x-pack/plugins/fleet/cypress/downloads/downloads.html rename x-pack/plugins/fleet/cypress/{integration/a11y/home_page.spec.ts => e2e/a11y/home_page.cy.ts} (98%) rename x-pack/plugins/fleet/cypress/{integration/agent_binary_download_source.spec.ts => e2e/agent_binary_download_source.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/agent_list.spec.ts => e2e/agent_list.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/agent_policy.spec.ts => e2e/agent_policy.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/enrollment_token.spec.ts => e2e/enrollment_token.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/fleet_agent_flyout.spec.ts => e2e/fleet_agent_flyout.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/fleet_settings.spec.ts => e2e/fleet_settings.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/fleet_startup.spec.ts => e2e/fleet_startup.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/install_assets.spec.ts => e2e/install_assets.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/integrations_mock.spec.ts => e2e/integrations_mock.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/integrations_real.spec.ts => e2e/integrations_real.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/package_policy.spec.ts => e2e/package_policy.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/privileges_fleet_all_integrations_none.spec.ts => e2e/privileges_fleet_all_integrations_none.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/privileges_fleet_all_integrations_read.spec.ts => e2e/privileges_fleet_all_integrations_read.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/{integration/privileges_fleet_none_integrations_all.spec.ts => e2e/privileges_fleet_none_integrations_all.cy.ts} (100%) rename x-pack/plugins/fleet/cypress/support/{index.ts => e2e.ts} (100%) create mode 100644 x-pack/plugins/osquery/cypress.config.ts delete mode 100644 x-pack/plugins/osquery/cypress/cypress.json rename x-pack/plugins/osquery/cypress/{integration/all/add_integration.spec.ts => e2e/all/add_integration.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/all/alerts.spec.ts => e2e/all/alerts.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/all/discover.spec.ts => e2e/all/discover.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/all/edit_saved_queries.spec.ts => e2e/all/edit_saved_queries.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/all/live_query.spec.ts => e2e/all/live_query.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/all/metrics.spec.ts => e2e/all/metrics.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/all/packs.spec.ts => e2e/all/packs.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/all/saved_queries.spec.ts => e2e/all/saved_queries.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/roles/admin.spec.ts => e2e/roles/admin.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/roles/alert_test.spec.ts => e2e/roles/alert_test.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/roles/reader.spec.ts => e2e/roles/reader.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/roles/t1_analyst.spec.ts => e2e/roles/t1_analyst.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/{integration/roles/t2_analyst.spec.ts => e2e/roles/t2_analyst.cy.ts} (100%) rename x-pack/plugins/osquery/cypress/support/{index.ts => e2e.ts} (100%) rename x-pack/plugins/security_solution/cypress/{ccs_integration/detection_alerts/alerts_details.spec.ts => ccs_e2e/detection_alerts/alerts_details.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{ccs_integration/detection_rules/event_correlation_rule.spec.ts => ccs_e2e/detection_rules/event_correlation_rule.cy.ts} (100%) create mode 100644 x-pack/plugins/security_solution/cypress/cypress.config.ts delete mode 100644 x-pack/plugins/security_solution/cypress/cypress.json create mode 100644 x-pack/plugins/security_solution/cypress/cypress_ci.config.ts delete mode 100644 x-pack/plugins/security_solution/cypress/cypress_ci.json rename x-pack/plugins/security_solution/cypress/{integration/cases/attach_alert_to_case.spec.ts => e2e/cases/attach_alert_to_case.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/cases/attach_timeline.spec.ts => e2e/cases/attach_timeline.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/cases/connector_options.spec.ts => e2e/cases/connector_options.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/cases/connectors.spec.ts => e2e/cases/connectors.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/cases/creation.spec.ts => e2e/cases/creation.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/cases/privileges.spec.ts => e2e/cases/privileges.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/dashboards/entity_analytics.spec.ts => e2e/dashboards/entity_analytics.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/data_sources/create_runtime_field.spec.ts => e2e/data_sources/create_runtime_field.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/data_sources/sourcerer.spec.ts => e2e/data_sources/sourcerer.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_alerts/alert_flyout.spec.ts => e2e/detection_alerts/alert_flyout.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_alerts/alerts_details.spec.ts => e2e/detection_alerts/alerts_details.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts => e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_alerts/building_block_alerts.spec.ts => e2e/detection_alerts/building_block_alerts.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_alerts/changing_alert_status.spec.ts => e2e/detection_alerts/changing_alert_status.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_alerts/cti_enrichments.spec.ts => e2e/detection_alerts/cti_enrichments.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_alerts/investigate_in_timeline.spec.ts => e2e/detection_alerts/investigate_in_timeline.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_alerts/missing_privileges_callout.spec.ts => e2e/detection_alerts/missing_privileges_callout.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_response/open_alerts_in_timeline.spec.ts => e2e/detection_response/open_alerts_in_timeline.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/all_rules_read_only.spec.ts => e2e/detection_rules/all_rules_read_only.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/bulk_edit_rules.spec.ts => e2e/detection_rules/bulk_edit_rules.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/custom_query_rule.spec.ts => e2e/detection_rules/custom_query_rule.cy.ts} (99%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/custom_query_rule_data_view.spec.ts => e2e/detection_rules/custom_query_rule_data_view.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/event_correlation_rule.spec.ts => e2e/detection_rules/event_correlation_rule.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/export_rule.spec.ts => e2e/detection_rules/export_rule.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/import_rules.spec.ts => e2e/detection_rules/import_rules.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/indicator_match_rule.spec.ts => e2e/detection_rules/indicator_match_rule.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/links.spec.ts => e2e/detection_rules/links.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/machine_learning_rule.spec.ts => e2e/detection_rules/machine_learning_rule.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/new_terms_rule.spec.ts => e2e/detection_rules/new_terms_rule.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/override.spec.ts => e2e/detection_rules/override.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/prebuilt_rules.spec.ts => e2e/detection_rules/prebuilt_rules.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/related_integrations.spec.ts => e2e/detection_rules/related_integrations.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/rules_selection.spec.ts => e2e/detection_rules/rules_selection.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/rules_table_auto_refresh.spec.ts => e2e/detection_rules/rules_table_auto_refresh.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/sorting.spec.ts => e2e/detection_rules/sorting.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/detection_rules/threshold_rule.spec.ts => e2e/detection_rules/threshold_rule.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/exceptions/alerts_table_flow/add_exception.spec.ts => e2e/exceptions/alerts_table_flow/add_exception.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/exceptions/exceptions_flyout.spec.ts => e2e/exceptions/exceptions_flyout.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/exceptions/exceptions_management_flow/all_exception_lists_read_only.spec.ts => e2e/exceptions/exceptions_management_flow/all_exception_lists_read_only.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/exceptions/exceptions_table.spec.ts => e2e/exceptions/exceptions_table.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration => e2e}/exceptions/rule_details_flow/add_exception.spec.ts (100%) rename x-pack/plugins/security_solution/cypress/{integration => e2e}/exceptions/rule_details_flow/add_exception_data_view.spect.ts (100%) rename x-pack/plugins/security_solution/cypress/{integration => e2e}/exceptions/rule_details_flow/edit_exception.spec.ts (100%) rename x-pack/plugins/security_solution/cypress/{integration => e2e}/exceptions/rule_details_flow/edit_exception_data_view.spec.ts (100%) rename x-pack/plugins/security_solution/cypress/{integration => e2e}/exceptions/rule_details_flow/read_only_view.spect.ts (100%) rename x-pack/plugins/security_solution/cypress/{integration/filters/pinned_filters.spec.ts => e2e/filters/pinned_filters.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/guided_onboarding/tour.spec.ts => e2e/guided_onboarding/tour.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/header/navigation.spec.ts => e2e/header/navigation.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/header/search_bar.spec.ts => e2e/header/search_bar.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/host_details/risk_tab.spec.ts => e2e/host_details/risk_tab.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/hosts/events_viewer.spec.ts => e2e/hosts/events_viewer.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/hosts/host_risk_tab.spec.ts => e2e/hosts/host_risk_tab.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/hosts/hosts_risk_column.spec.ts => e2e/hosts/hosts_risk_column.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/hosts/inspect.spec.ts => e2e/hosts/inspect.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/ml/ml_conditional_links.spec.ts => e2e/ml/ml_conditional_links.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/network/hover_actions.spec.ts => e2e/network/hover_actions.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/network/inspect.spec.ts => e2e/network/inspect.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/network/overflow_items.spec.ts => e2e/network/overflow_items.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/overview/cti_link_panel.spec.ts => e2e/overview/cti_link_panel.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/overview/overview.spec.ts => e2e/overview/overview.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/pagination/pagination.spec.ts => e2e/pagination/pagination.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timeline_templates/creation.spec.ts => e2e/timeline_templates/creation.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timeline_templates/export.spec.ts => e2e/timeline_templates/export.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/creation.spec.ts => e2e/timelines/creation.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/data_providers.spec.ts => e2e/timelines/data_providers.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/export.spec.ts => e2e/timelines/export.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/fields_browser.spec.ts => e2e/timelines/fields_browser.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/flyout_button.spec.ts => e2e/timelines/flyout_button.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/full_screen.spec.ts => e2e/timelines/full_screen.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/inspect.spec.ts => e2e/timelines/inspect.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/local_storage.spec.ts => e2e/timelines/local_storage.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/notes_tab.spec.ts => e2e/timelines/notes_tab.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/open_timeline.spec.ts => e2e/timelines/open_timeline.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/overview.tsx => e2e/timelines/overview.cy.tsx} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/pagination.spec.ts => e2e/timelines/pagination.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/query_tab.spec.ts => e2e/timelines/query_tab.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/row_renderers.spec.ts => e2e/timelines/row_renderers.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/search_or_filter.spec.ts => e2e/timelines/search_or_filter.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/timelines/toggle_column.spec.ts => e2e/timelines/toggle_column.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/urls/compatibility.spec.ts => e2e/urls/compatibility.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/urls/not_found.spec.ts => e2e/urls/not_found.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/urls/state.spec.ts => e2e/urls/state.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/users/inspect.spec.ts => e2e/users/inspect.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/users/user_details.spec.ts => e2e/users/user_details.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/users/users_tabs.spec.ts => e2e/users/users_tabs.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{integration/value_lists/value_lists.spec.ts => e2e/value_lists/value_lists.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/support/{index.js => e2e.js} (100%) rename x-pack/plugins/security_solution/cypress/{upgrade_integration/detections/detection_rules/custom_query_rule.spec.ts => upgrade_e2e/detections/detection_rules/custom_query_rule.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{upgrade_integration/detections/detection_rules/threshold_rule.spec.ts => upgrade_e2e/detections/detection_rules/threshold_rule.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{upgrade_integration/threat_hunting/cases/import_case.spec.ts => upgrade_e2e/threat_hunting/cases/import_case.cy.ts} (100%) rename x-pack/plugins/security_solution/cypress/{upgrade_integration/threat_hunting/timeline/import_timeline.spec.ts => upgrade_e2e/threat_hunting/timeline/import_timeline.cy.ts} (100%) create mode 100644 x-pack/plugins/threat_intelligence/cypress/cypress.config.ts delete mode 100644 x-pack/plugins/threat_intelligence/cypress/cypress.json rename x-pack/plugins/threat_intelligence/cypress/{integration/empty_page.spec.ts => e2e/empty_page.cy.ts} (100%) rename x-pack/plugins/threat_intelligence/cypress/{integration/indicators.spec.ts => e2e/indicators.cy.ts} (100%) rename x-pack/plugins/threat_intelligence/cypress/{integration/query_bar.spec.ts => e2e/query_bar.cy.ts} (100%) rename x-pack/plugins/threat_intelligence/cypress/{integration/timeline.spec.ts => e2e/timeline.cy.ts} (100%) rename x-pack/plugins/threat_intelligence/cypress/support/{index.js => e2e.js} (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9df9f99c02adc..89f74e669e9cf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -383,10 +383,10 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/security_solution/common/search_strategy/timeline @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/common/types/timeline @elastic/security-threat-hunting-investigations -/x-pack/plugins/security_solution/cypress/integration/timeline_templates @elastic/security-threat-hunting-investigations -/x-pack/plugins/security_solution/cypress/integration/timeline @elastic/security-threat-hunting-investigations -/x-pack/plugins/security_solution/cypress/integration/detection_alerts @elastic/security-threat-hunting-investigations -/x-pack/plugins/security_solution/cypress/integration/urls @elastic/security-threat-hunting-investigations +/x-pack/plugins/security_solution/cypress/e2e/timeline_templates @elastic/security-threat-hunting-investigations +/x-pack/plugins/security_solution/cypress/e2e/timeline @elastic/security-threat-hunting-investigations +/x-pack/plugins/security_solution/cypress/e2e/detection_alerts @elastic/security-threat-hunting-investigations +/x-pack/plugins/security_solution/cypress/e2e/urls @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/common/components/alerts_viewer @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_action @elastic/security-threat-hunting-investigations @@ -407,18 +407,18 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/security_solution/common/search_strategy/security_solution/network @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/common/search_strategy/security_solution/user @elastic/security-threat-hunting-explore -/x-pack/plugins/security_solution/cypress/integration/cases @elastic/security-threat-hunting-explore -/x-pack/plugins/security_solution/cypress/integration/host_details @elastic/security-threat-hunting-explore -/x-pack/plugins/security_solution/cypress/integration/hosts @elastic/security-threat-hunting-explore -/x-pack/plugins/security_solution/cypress/integration/network @elastic/security-threat-hunting-explore -/x-pack/plugins/security_solution/cypress/integration/overview @elastic/security-threat-hunting-explore -/x-pack/plugins/security_solution/cypress/integration/pagination @elastic/security-threat-hunting-explore -/x-pack/plugins/security_solution/cypress/integration/users @elastic/security-threat-hunting-explore +/x-pack/plugins/security_solution/cypress/e2e/cases @elastic/security-threat-hunting-explore +/x-pack/plugins/security_solution/cypress/e2e/host_details @elastic/security-threat-hunting-explore +/x-pack/plugins/security_solution/cypress/e2e/hosts @elastic/security-threat-hunting-explore +/x-pack/plugins/security_solution/cypress/e2e/network @elastic/security-threat-hunting-explore +/x-pack/plugins/security_solution/cypress/e2e/overview @elastic/security-threat-hunting-explore +/x-pack/plugins/security_solution/cypress/e2e/pagination @elastic/security-threat-hunting-explore +/x-pack/plugins/security_solution/cypress/e2e/users @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/cypress/screens/hosts @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/cypress/screens/network @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/cypress/tasks/hosts @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/cypress/tasks/network @elastic/security-threat-hunting-explore -/x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/cases @elastic/security-threat-hunting-explore +/x-pack/plugins/security_solution/cypress/upgrade_e2e/threat_hunting/cases @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/public/common/components/charts @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/public/common/components/header_page @elastic/security-threat-hunting-explore @@ -463,7 +463,7 @@ x-pack/examples/files_example @elastic/kibana-app-services ## Security Solution sub teams - Detections and Response Rules -/x-pack/plugins/security_solution/cypress/integration/detection_rules @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/cypress/e2e/detection_rules @elastic/security-detections-response-rules /x-pack/plugins/security_solution/public/detections/components/rules @elastic/security-detections-response-rules /x-pack/plugins/security_solution/public/detections/components/severity @elastic/security-detections-response-rules @@ -488,9 +488,9 @@ x-pack/examples/files_example @elastic/kibana-app-services ## Security Solution sub teams - Security Platform /x-pack/plugins/lists @elastic/security-solution-platform -/x-pack/plugins/security_solution/cypress/integration/data_sources @elastic/security-solution-platform -/x-pack/plugins/security_solution/cypress/integration/exceptions @elastic/security-solution-platform -/x-pack/plugins/security_solution/cypress/integration/value_lists @elastic/security-solution-platform +/x-pack/plugins/security_solution/cypress/e2e/data_sources @elastic/security-solution-platform +/x-pack/plugins/security_solution/cypress/e2e/exceptions @elastic/security-solution-platform +/x-pack/plugins/security_solution/cypress/e2e/value_lists @elastic/security-solution-platform /x-pack/plugins/security_solution/public/common/components/exceptions @elastic/security-solution-platform /x-pack/plugins/security_solution/public/exceptions @elastic/security-solution-platform @@ -540,8 +540,8 @@ x-pack/plugins/security_solution/server/usage/ @elastic/security-data-analytics x-pack/plugins/security_solution/server/lib/telemetry/ @elastic/security-data-analytics ## Security Solution sub teams - security-engineering-productivity -x-pack/plugins/security_solution/cypress/ccs_integration @elastic/security-engineering-productivity -x-pack/plugins/security_solution/cypress/upgrade_integration @elastic/security-engineering-productivity +x-pack/plugins/security_solution/cypress/ccs_e2e @elastic/security-engineering-productivity +x-pack/plugins/security_solution/cypress/upgrade_e2e @elastic/security-engineering-productivity x-pack/plugins/security_solution/cypress/README.md @elastic/security-engineering-productivity x-pack/test/security_solution_cypress @elastic/security-engineering-productivity @@ -571,7 +571,7 @@ x-pack/test/threat_intelligence_cypress @elastic/protections-experience # Security Solution onboarding tour /x-pack/plugins/security_solution/public/common/components/guided_onboarding @elastic/platform-onboarding -/x-pack/plugins/security_solution/cypress/integration/guided_onboarding @elastic/platform-onboarding +/x-pack/plugins/security_solution/cypress/e2e/guided_onboarding @elastic/platform-onboarding # Design (at the bottom for specificity of SASS files) **/*.scss @elastic/kibana-design diff --git a/package.json b/package.json index 9947161017b8a..c8c1aa501b981 100644 --- a/package.json +++ b/package.json @@ -650,9 +650,9 @@ "@babel/types": "^7.19.0", "@bazel/ibazel": "^0.16.2", "@bazel/typescript": "4.6.2", - "@cypress/code-coverage": "^3.9.12", + "@cypress/code-coverage": "^3.10.0", "@cypress/snapshot": "^2.1.7", - "@cypress/webpack-preprocessor": "^5.6.0", + "@cypress/webpack-preprocessor": "^5.12.2", "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^6.0.0", @@ -1228,14 +1228,14 @@ "cssnano": "^5.1.12", "cssnano-preset-default": "^5.2.12", "csstype": "^3.0.2", - "cypress": "^9.6.1", - "cypress-axe": "^0.14.0", + "cypress": "^10.7.0", + "cypress-axe": "^1.0.0", "cypress-file-upload": "^5.0.8", - "cypress-multi-reporters": "^1.6.0", + "cypress-multi-reporters": "^1.6.1", "cypress-pipe": "^2.0.0", - "cypress-react-selector": "^2.3.17", - "cypress-real-events": "^1.7.0", - "cypress-recurse": "^1.20.0", + "cypress-react-selector": "^3.0.0", + "cypress-real-events": "^1.7.1", + "cypress-recurse": "^1.23.0", "debug": "^2.6.9", "delete-empty": "^2.0.0", "dependency-check": "^4.1.0", diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/cypress_report.xml b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/cypress_report.xml index ed0e154552caa..c0c66e81db26d 100644 --- a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/cypress_report.xml +++ b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/cypress_report.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts index 220a336915bf1..3b71823ee6bde 100644 --- a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts +++ b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts @@ -282,9 +282,9 @@ it('rewrites cypress reports with minimal changes', async () => { -‹?xml version="1.0" encoding="UTF-8"?› +‹?xml version="1.0" encoding="utf-8"?› ‹testsuites name="Mocha Tests" time="16.198" tests="2" failures="1"› - - ‹testsuite name="Root Suite" timestamp="2020-07-22T15:06:26" tests="0" file="cypress/integration/timeline_flyout_button.spec.ts" failures="0" time="0"› + - ‹testsuite name="Root Suite" timestamp="2020-07-22T15:06:26" tests="0" file="cypress/e2e/timeline_flyout_button.spec.ts" failures="0" time="0"› - ‹/testsuite› - + ‹testsuite name="Root Suite" timestamp="2020-07-22T15:06:26" tests="0" file="cypress/integration/timeline_flyout_button.spec.ts" failures="0" time="0"/› + + ‹testsuite name="Root Suite" timestamp="2020-07-22T15:06:26" tests="0" file="cypress/e2e/timeline_flyout_button.spec.ts" failures="0" time="0"/› ‹testsuite name="timeline flyout button" timestamp="2020-07-22T15:06:26" tests="2" failures="1" time="16.198"› - ‹testcase name="timeline flyout button toggles open the timeline" time="8.099" classname="toggles open the timeline"› - ‹/testcase› diff --git a/x-pack/plugins/apm/ftr_e2e/cypress.config.ts b/x-pack/plugins/apm/ftr_e2e/cypress.config.ts new file mode 100644 index 0000000000000..7a92b84ac36bd --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress.config.ts @@ -0,0 +1,36 @@ +/* + * 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 { defineConfig } from 'cypress'; +import { plugin } from './cypress/plugins'; + +module.exports = defineConfig({ + fileServerFolder: './cypress', + fixturesFolder: './cypress/fixtures', + screenshotsFolder: './cypress/screenshots', + videosFolder: './cypress/videos', + requestTimeout: 10000, + responseTimeout: 40000, + defaultCommandTimeout: 30000, + execTimeout: 120000, + pageLoadTimeout: 120000, + viewportHeight: 900, + viewportWidth: 1440, + video: false, + screenshotOnRunFailure: false, + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + plugin(on, config); + }, + baseUrl: 'http://localhost:5601', + supportFile: './cypress/support/e2e.ts', + specPattern: './cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', + experimentalSessionAndOrigin: false, + }, +}); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress.json b/x-pack/plugins/apm/ftr_e2e/cypress.json deleted file mode 100644 index 848a10efed668..0000000000000 --- a/x-pack/plugins/apm/ftr_e2e/cypress.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "fileServerFolder": "./cypress", - "fixturesFolder": "./cypress/fixtures", - "integrationFolder": "./cypress/integration", - "pluginsFile": "./cypress/plugins/index.ts", - "screenshotsFolder": "./cypress/screenshots", - "supportFile": "./cypress/support/index.ts", - "videosFolder": "./cypress/videos", - "requestTimeout": 10000, - "responseTimeout": 40000, - "defaultCommandTimeout": 30000, - "execTimeout": 120000, - "pageLoadTimeout": 120000, - "viewportHeight": 900, - "viewportWidth": 1440, - "video": false, - "screenshotOnRunFailure": false, - "experimentalSessionAndOrigin": true -} diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/feature_flag/comparison.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/feature_flag/comparison.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/infrastructure/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/infrastructure/generate_data.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/infrastructure/generate_data.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/infrastructure/generate_data.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/infrastructure/infrastructure_page.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/infrastructure/infrastructure_page.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/infrastructure/infrastructure_page.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/infrastructure/infrastructure_page.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/integration_settings/integration_policy.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/integration_settings/integration_policy.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/no_data_screen.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/no_data_screen.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/no_data_screen.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/no_data_screen.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/rules/error_count.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/rules/error_count.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/rules/error_count.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/rules/error_count.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/agent_configurations.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/agent_configurations.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/custom_links.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/custom_links.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/deep_links.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/deep_links.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/dependencies.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/dependencies.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/error_details.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/error_details.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/errors_page.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/errors_page.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/generate_data.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/generate_data.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/generate_data.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts similarity index 95% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts index be9acfd38ab0c..2ee2f4f019b12 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts @@ -31,6 +31,10 @@ describe('Home page', () => { to: new Date(end).getTime(), }) ); + + cy.updateAdvancedSettings({ + 'observability:enableComparisonByDefault': true, + }); }); after(() => { diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/generate_data.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/generate_data.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/generate_data.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/generate_data.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/generate_data.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/generate_data.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/header_filters.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/header_filters.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/service_inventory.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/service_inventory.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/aws_lambda/aws_lamba.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/aws_lamba.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/aws_lambda/aws_lamba.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/aws_lamba.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/aws_lambda/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/generate_data.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/aws_lambda/generate_data.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/generate_data.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/errors_table.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/errors_table.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/header_filters.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/header_filters.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/instances_table.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/instances_table.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/time_comparison.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/time_comparison.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transaction_details/generate_span_links_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/generate_span_links_data.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transaction_details/generate_span_links_data.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/generate_span_links_data.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transaction_details/span_links.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transaction_details/span_links.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transaction_details/transaction_details.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transaction_details/transaction_details.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transactions_overview/transactions_overview.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/transactions_overview/transactions_overview.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/tutorial/tutorial.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/tutorial/tutorial.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/tutorial/tutorial.spec.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/tutorial/tutorial.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts b/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts index 2cc7595ce6731..8adaad0b71c63 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts @@ -29,7 +29,7 @@ import { createEsClientForTesting } from '@kbn/test'; * @type {Cypress.PluginConfig} */ -const plugin: Cypress.PluginConfig = (on, config) => { +export const plugin: Cypress.PluginConfig = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config @@ -66,5 +66,3 @@ const plugin: Cypress.PluginConfig = (on, config) => { }, }); }; - -module.exports = plugin; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts index 37182e328ebf3..692926d3049ca 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts @@ -21,25 +21,26 @@ Cypress.Commands.add('loginAsEditorUser', () => { Cypress.Commands.add( 'loginAs', ({ username, password }: { username: string; password: string }) => { - cy.log(`Calling 'loginAs'`); - cy.session([username, password], () => { - cy.log(`Logging in as ${username}`); - const kibanaUrl = Cypress.env('KIBANA_URL'); - cy.request({ - log: false, - method: 'POST', - url: `${kibanaUrl}/internal/security/login`, - body: { - providerType: 'basic', - providerName: 'basic', - currentURL: `${kibanaUrl}/login`, - params: { username, password }, - }, - headers: { - 'kbn-xsrf': 'e2e_test', - }, - }); + // cy.session(username, () => { + const kibanaUrl = Cypress.env('KIBANA_URL'); + cy.log(`Logging in as ${username} on ${kibanaUrl}`); + cy.visit('/'); + cy.request({ + log: true, + method: 'POST', + url: `${kibanaUrl}/internal/security/login`, + body: { + providerType: 'basic', + providerName: 'basic', + currentURL: `${kibanaUrl}/login`, + params: { username, password }, + }, + headers: { + 'kbn-xsrf': 'e2e_test', + }, + // }); }); + cy.visit('/'); } ); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/index.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/e2e.ts similarity index 80% rename from x-pack/plugins/apm/ftr_e2e/cypress/support/index.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/support/e2e.ts index 48367848ed48f..93daa0bc7ed2a 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/index.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/e2e.ts @@ -5,5 +5,9 @@ * 2.0. */ +Cypress.on('uncaught:exception', (err, runnable) => { + return false; +}); + import './commands'; // import './output_command_timings'; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts b/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts index 86316fe7ef8c8..9736a695e81c7 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts @@ -58,10 +58,9 @@ export async function cypressTestRunner({ getService }: FtrProviderContext) { ...cypressCliArgs, project: cypressProjectPath, config: { - baseUrl: kibanaUrl, - requestTimeout: 10000, - responseTimeout: 60000, - defaultCommandTimeout: 15000, + e2e: { + baseUrl: kibanaUrl, + }, }, env: { KIBANA_URL: kibanaUrl, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress.config.js b/x-pack/plugins/enterprise_search/public/applications/app_search/cypress.config.js new file mode 100644 index 0000000000000..b625b3051f15a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/cypress.config.js @@ -0,0 +1,34 @@ +/* + * 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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'cypress'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig({ + defaultCommandTimeout: 120000, + e2e: { + baseUrl: 'http://localhost:5601', + // eslint-disable-next-line no-unused-vars + setupNodeEvents(on, config) {}, + supportFile: './cypress/support/commands.ts', + }, + env: { + password: 'changeme', + username: 'elastic', + }, + execTimeout: 120000, + pageLoadTimeout: 180000, + retries: { + runMode: 2, + }, + screenshotsFolder: '../../../target/cypress/screenshots', + video: false, + videosFolder: '../../../target/cypress/videos', + viewportHeight: 1200, + viewportWidth: 1600, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress.json b/x-pack/plugins/enterprise_search/public/applications/app_search/cypress.json deleted file mode 100644 index 766aaf6df36ad..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "supportFile": "./cypress/support/commands.ts", - "pluginsFile": false, - "retries": { - "runMode": 2 - }, - "baseUrl": "http://localhost:5601", - "env": { - "username": "elastic", - "password": "changeme" - }, - "screenshotsFolder": "../../../target/cypress/screenshots", - "videosFolder": "../../../target/cypress/videos", - "defaultCommandTimeout": 120000, - "execTimeout": 120000, - "pageLoadTimeout": 180000, - "viewportWidth": 1600, - "viewportHeight": 1200, - "video": false -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/integration/engines.spec.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/e2e/engines.cy.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/app_search/cypress/integration/engines.spec.ts rename to x-pack/plugins/enterprise_search/public/applications/app_search/cypress/e2e/engines.cy.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress.config.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress.config.js new file mode 100644 index 0000000000000..a6d98df28c413 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress.config.js @@ -0,0 +1,35 @@ +/* + * 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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'cypress'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig({ + defaultCommandTimeout: 120000, + e2e: { + baseUrl: 'http://localhost:5601', + // eslint-disable-next-line no-unused-vars + setupNodeEvents(on, config) {}, + supportFile: false, + }, + env: { + password: 'changeme', + username: 'elastic', + }, + execTimeout: 120000, + fixturesFolder: false, + pageLoadTimeout: 180000, + retries: { + runMode: 2, + }, + screenshotsFolder: '../../../target/cypress/screenshots', + video: false, + videosFolder: '../../../target/cypress/videos', + viewportHeight: 1200, + viewportWidth: 1600, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress.json b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress.json deleted file mode 100644 index 8ca8bdfd79a49..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "supportFile": false, - "pluginsFile": false, - "retries": { - "runMode": 2 - }, - "baseUrl": "http://localhost:5601", - "env": { - "username": "elastic", - "password": "changeme" - }, - "fixturesFolder": false, - "screenshotsFolder": "../../../target/cypress/screenshots", - "videosFolder": "../../../target/cypress/videos", - "defaultCommandTimeout": 120000, - "execTimeout": 120000, - "pageLoadTimeout": 180000, - "viewportWidth": 1600, - "viewportHeight": 1200, - "video": false -} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress/integration/overview.spec.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress/e2e/overview.cy.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress/integration/overview.spec.ts rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress/e2e/overview.cy.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress.config.js b/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress.config.js new file mode 100644 index 0000000000000..b625b3051f15a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress.config.js @@ -0,0 +1,34 @@ +/* + * 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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'cypress'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig({ + defaultCommandTimeout: 120000, + e2e: { + baseUrl: 'http://localhost:5601', + // eslint-disable-next-line no-unused-vars + setupNodeEvents(on, config) {}, + supportFile: './cypress/support/commands.ts', + }, + env: { + password: 'changeme', + username: 'elastic', + }, + execTimeout: 120000, + pageLoadTimeout: 180000, + retries: { + runMode: 2, + }, + screenshotsFolder: '../../../target/cypress/screenshots', + video: false, + videosFolder: '../../../target/cypress/videos', + viewportHeight: 1200, + viewportWidth: 1600, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress.json b/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress.json deleted file mode 100644 index 766aaf6df36ad..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "supportFile": "./cypress/support/commands.ts", - "pluginsFile": false, - "retries": { - "runMode": 2 - }, - "baseUrl": "http://localhost:5601", - "env": { - "username": "elastic", - "password": "changeme" - }, - "screenshotsFolder": "../../../target/cypress/screenshots", - "videosFolder": "../../../target/cypress/videos", - "defaultCommandTimeout": 120000, - "execTimeout": 120000, - "pageLoadTimeout": 180000, - "viewportWidth": 1600, - "viewportHeight": 1200, - "video": false -} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress/integration/overview.spec.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress/e2e/overview.cy.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress/integration/overview.spec.ts rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress/e2e/overview.cy.ts diff --git a/x-pack/plugins/fleet/cypress.config.ts b/x-pack/plugins/fleet/cypress.config.ts new file mode 100644 index 0000000000000..e2d5ffd3ffdac --- /dev/null +++ b/x-pack/plugins/fleet/cypress.config.ts @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'cypress'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig({ + defaultCommandTimeout: 60000, + requestTimeout: 60000, + responseTimeout: 60000, + execTimeout: 120000, + pageLoadTimeout: 120000, + + retries: { + runMode: 2, + }, + + screenshotsFolder: '../../../target/kibana-fleet/cypress/screenshots', + trashAssetsBeforeRuns: false, + video: false, + videosFolder: '../../../target/kibana-fleet/cypress/videos', + viewportHeight: 900, + viewportWidth: 1440, + screenshotOnRunFailure: true, + + env: { + protocol: 'http', + hostname: 'localhost', + configport: '5601', + }, + + e2e: { + baseUrl: 'http://localhost:5601', + setupNodeEvents(on, config) { + // eslint-disable-next-line @typescript-eslint/no-var-requires, @kbn/imports/no_boundary_crossing + return require('./cypress/plugins')(on, config); + }, + }, +}); diff --git a/x-pack/plugins/fleet/cypress/cypress.json b/x-pack/plugins/fleet/cypress/cypress.json deleted file mode 100644 index b36d0c513116c..0000000000000 --- a/x-pack/plugins/fleet/cypress/cypress.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "baseUrl": "http://localhost:5620", - "defaultCommandTimeout": 60000, - "requestTimeout": 60000, - "responseTimeout": 60000, - "execTimeout": 120000, - "pageLoadTimeout": 120000, - "nodeVersion": "system", - "retries": { - "runMode": 2 - }, - "screenshotsFolder": "../../../target/kibana-fleet/cypress/screenshots", - "trashAssetsBeforeRuns": false, - "video": false, - "videosFolder": "../../../target/kibana-fleet/cypress/videos", - "viewportHeight": 900, - "viewportWidth": 1440, - "screenshotOnRunFailure": true, - "env": { - "protocol": "http", - "hostname": "localhost", - "configport": "5601" - } -} diff --git a/x-pack/plugins/fleet/cypress/downloads/downloads.html b/x-pack/plugins/fleet/cypress/downloads/downloads.html deleted file mode 100644 index 772778ea352e58515ffdccdd56b8f8f73a6b15c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4956 zcmZ{ncQhPMzs9kwzDkrJiQapnu1*M1qeSnW6+~}~RfAQcBw1mJAX-Fp37h=1=!>W; zNc6BaYOuT9yM(MZ&s%afTO$e&Q>A@ijShd z!f<=H?XlS^EC~H%_Q5KP?7oFJ(xb$KM{a_)k#cl8Lq0Hgbc z+Zx;#mn@L8Pm|}|<`ut5W2@lbOZ^z$aO==d`SDT;3UjxG3{h~5*?-_$qshD%g{L`E zgNRDytK~u*3Yy7t<+jTTCm!$$c&@AcTsqTvBKtwm~LtS*NB_G12xX!3_Z7eXe5?^nGxg`>0pGscpEhhY%)h7Bb5vvJ&y=OT`xk*kJ`~^+j?YYwhyef@fZ7LbVsW2)|)5e!~k9j zDW^n!uTfz=KE@u_XEz|BUo#o=fa(R0x!`kXy7H~y#S%Q5{Z z9=k8Ze#o+GkLZ1<@Ksb|;I4^9KR;SRf&K1=vX!wla0{YNALQC(eQzC}=EN~#BvC)4 zz4?{2hA*hcHi^2gBOzb)qy))81|TINWlkW6w`$H^$dhH)KXt4$r2lU8)8Rzh2IafJ zM!7A@V;<=`xN!LM{RZvu3k=ZfFe!v@n2|N1I z)!eug$neEAjs)%PHN!AB+|R5Ge0rf$Bky;oFxTvy=*rb}`SB6as(tkqI;UpOoB42W z6?(tSU&@uc3`MG2@rZXG#Fg#+r>$)REabBw*5WLZHyr+1dR$Y+{vXyK^x|YV-F^$q zg{?EkD5Ur&%js{76HS~p$$CYsL>x>b5)ooL@Qg55wi{=oPD9u0_7WLMO*yrZTSyG^ zqOX3#1teuVm#l`{{DHdCu|{WfX5&HfqgeEigz>_1#Fw9mA?Pmdn&Gy}5$uOC3yL8+ya}&s;u`7BaVszCU7gT130CE-*Dk>@x zO&KbyKR?TVi~f%b2=wrTNcukV3X}?RTU%wBKMU_I-T>auG9ocEq@-d19xZ_WT%-Fl z|No%B)qe#^g?d4JLp@uI;~|z-Y=eR21!SysmJ(C_e;zpbqIP0NN?S_`tn#aQUfAT0 z3nf^lWGB38UOri_O4b-+bj2ez#5@g2_s!Z>NPYgc>%KzYF+-6ho9$AAlv@KU(*yCZ z=3u-Uu5_*wH)wf1)j+)@sxe~xd^u!a4cEh+FO>Yo&bduh_jRURo|yUF_xk_z+-1s{ zAj1o2nK4S=rTAjQ+vmaSoUaM`^k|ZuCS~~iNW!e8GS=Q;V-{{dTvBXUOOr`kN8_q! z&|H#!75H#*HY#>JRnHl1u@$}_qDZa^Mb5*BYC)lNp-EC`YVapsG{{ctjt|*M8JlH- z>MDt-0Eg*k9LcALyCssg{Rl8cUl9{q^aAjU6D?d?_CYJVI!{i1g|wRdB& z8ntMWtc_jeQ6dK=fr7|GVTxMpN;F-c@vGOBK+&J5(X=K>&1DQP0Yn!@2l8j18LtB? zGzr(DHTm?(hIlAm!{l;7w-W#oKwBQlb(rcZS01taa=)`n;%X#k#s28kd))N$g?v26 z8D`Nsio3~kKPIYC90UJQMIhev#E)`&Z^XZbI7dd|(!2`-R=C8EC=0Oos|(9ZR!_{q zP(2~p|2vjHe(~r##1r2u{BTp+vdy$GaKgC;!rxzhGJHxxl2Ol@A zp!eX?Fg1YJshcg^Al(^nnyNikM$rOOK!ZRqomH7_l6#uMk=K?CLV6(Xp3!+LY09@k z=OktZY%?}JHzQBVWtH-OTVRJuQqSo=PQR`KKO4NaVxt{G@d~UZ`Q?5AUrd#oQ-WVE zMXGFijHb#PAV@});)uEhARUz8dR>Da1d#qh_N0vaWGxO2%+NaYur&mCLd~#2Mo&Lu zH=^GGJQAi=O?PX!Z%i-rUa!90GQF*4wX616HbWMEW?^(|OSyx(o}nH2wjBkK=#~m8 zZ|A4x(h`m2C6v7K$QGPAdFiwO<;PUFHKt+4(U@_PnWeUZfIyOn#Qf_F1`d=}%_Z~@ zjhhlA94@?Y?{`!ZLft&}U)K6Lf3mj{J(nV0?HBKzGzI$U2!13VT{~!(B$RYsL{#OK z=4?dVw~?Mv;|!5b!8A^;-P7q;WXnc6@n?28-%%dGWvAl7g{`c?80}n!+29q*f;Q@< z@SFa&BiL$ZhV~c#1kjv!C%9D-G@KS1F=bZ)TpYLwv2G2P3P*Q2=-t>GyBTY^dak@M zq;?}_TtyJyT@+x^g98TU%y0FMVCUX7s{Y~{AN-Uarw%?-iP~F}9jBaWmK&$+t`1TE z>i)F!*`N}r&N!;(M)Rhpe0yb78?);00a(UsY^0*zm!kdZ=()UfC!vRi;693YW|cvI zu~Hd?dbmSGs4h3wAOh7i#20{ZzX|gG(2Gk!JPHs|dj6`0>Jrltr+}}n{OoXQ`tzhqy~MrJZ|KKwql|KR zkycyD(29YFCujtKSg;OnY08V<<84R7)eL zaSEsYokSgoMkzM_*5^y!N2pw^29;McF<;>{rh}LSiNB3;=6c_*F^av@fd#DIYD39+ zwBpX%Ay*_AWZ$WCrc1m^yYr{ht)r_im*NVi_MOC7rd!hy{>K0KBRGZ;1AC2WDCGF% zKKtQI{qd3)b}BQef>+)fzj5AIFyiiRy1+7fb>Fkc`fJGpeDDZ_h1{SobgHS0j*d4m zpp1l$SNUcci-X=OL!xRBr&a)fqKuZ*5YRx?CjxHfR{BIuB@Pe{B9Z>Htcn1?iI~~0 zQqkygDrFfUXh3ea1z|#T7g}_qV(28&0$On1wk0~NjE+yAbyWmh3`8_3aop7$)?^3C z?UYfFa{WWvOMAhXZjOe7suFm>m^2giBq{Pwq#%<*(t{}k6X`HX0M#rbtPOC?mk^b| z9_8ZnQdQ&Vv8wo0#m)G&uSZ@P?xceyhX%NB$W(2)+3s6N%uKHBZupGHtYU}1>I6mb zBG)JHE0Z~soVjbq9f0BIuYN~BV$HHMjn${n3z>#=2N&Vj_k~>$Mi?|dUoG!jI0V($YXo7Q1&)K`;TVmZ+mbss~wRE>`BePf&@2xj#`1m*Rno@hNkx zOB;c2#1B5B2Gb-uV3)w8dPqfS@WDn6uhvsp@4NQGDdeYZ@eJLK}bBf zysZ~)&L%DiMPh7st4Hh+NG09r4_mLREOoDq-9hYc#^~MG?M97mbSzWrdOS>Qx zH=L8@ch1G!j8B$Q;4erLqRl%Vw zUExboj*Ib2MRun@qUw6IYoxVi?qTaL*=f{5J{%X~lLS$Lp;0Na)r}`NT*= zB3@*Ln=Z8!(eG=%Yx=AfJS4Ym8@F7WHlA5$?A{HCR_N1bC^QN)iVs@0liry(I+vX@ zq+C?L#eXxpQ*LfJv9aELQ}))KP1P`Dj$^4&aqI2&V>{c7gUisY+$25*Y_mzqwB}%?A%+Qo;>&W*s?zCv5{yI6`W!$&IimQT41}0wSwL}oRYdu zIT-f(LrTZx3}C!toQ%vQ;&7+2!EU?DvejTj)<^V7N)ey6CQ@Z>caL_KAhAXG`aHA% zdebRpS*tgilcDP^o7~QJN;bCrb?-N`EoyZ4G?ne`0iP+whmH#h?p5J32zNqq zS4&on9h+0jW@ezYU#s2B9Xd7BfrmNZhO90r8}|a={0|C!hwX|DH9;$nM8)fU)*c~I zpB;B-x`Z8)VqGdS-$o$Z3lqaJZuR2k^tFW9THhze^RYd_%X#%=){@P;x}ATkVj{YdVComeXNr}?@;~l zhbFGDSMTa*KAvDYnKj{z+T=I7G<`vsZe|F*#2i}4xvyG}R~xw}l&)FCRR>@8o5>A+ z;8J&%*<5+;Dm9T-RDQi-gYSmbXxNV(riyI55}Sd>;P!l=b3Z+&x}xv%-X@n{KJ_w(Gj76brN%n2M(W;m zQoWTe;~t#r3T5N+)S{T%CcS^%uHK61o=TL`=^+16)K=B*fPc{OOEUR#{|W3bl)&DV zHBQ;P3!zGS9@hjJofnRVCcfo3>D|;}y0{x*U1AR5X(L{9I-`*ehqZd_`f2 zODm_eT4lX0yxVuM)o1_1x7F5&h7RRcdq8v_TWbApv9r#SGdiXoYuUbu0{5pSt(p*&fui@oCTT&~Xe)!b7%=Fn1pf=RAp?B9GPTQ^oCR2(o@IsW7!OYJy7 zLE)N_!o=rI)tKVpU~Suqh56UsAK$YSEBoA0gc-lRT03P+u0L&A5fM4Ha|tZ+Gkvmp zb`r>?l5ZStk~P%ip+-YH(qV~3H~BWX38x@;npIxa9?bc}(q*KqxPyfnj8~6dL`TyX5$ zeB^Gl&fyF0kN0=l9g8=%#*)rHYD1k8H{#Q`uq^^u4&CyTgxLCL__-_Yzu34MKPf@{ zgO?r(f3cC2j0NyN1p1#y`IkWdEm9Hz(!cJ1@#z0;{m ../../../target/kibana-fleet/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-fleet/cypress/results/output.json --reportDir ../../../target/kibana-fleet/cypress/results && mkdir -p ../../../target/junit && cp ../../../target/kibana-fleet/cypress/results/*.xml ../../../target/junit/" } } diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index d0361f4550576..7cc16fe654268 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -6,8 +6,9 @@ "declaration": true, "declarationMap": true, }, + "exclude": ["cypress.config.ts"], "include": [ - // add all the folders containg files to be compiled + // add all the folders containing files to be compiled ".storybook/**/*", "common/**/*", "public/**/*", @@ -15,6 +16,7 @@ "server/**/*.json", "scripts/**/*", "package.json", + "cypress.config.ts", "../../../typings/**/*" ], "references": [ diff --git a/x-pack/plugins/osquery/cypress.config.ts b/x-pack/plugins/osquery/cypress.config.ts new file mode 100644 index 0000000000000..862b4a916bead --- /dev/null +++ b/x-pack/plugins/osquery/cypress.config.ts @@ -0,0 +1,42 @@ +/* + * 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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'cypress'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig({ + defaultCommandTimeout: 60000, + execTimeout: 120000, + pageLoadTimeout: 12000, + + retries: { + runMode: 1, + openMode: 0, + }, + + screenshotsFolder: '../../../target/kibana-osquery/cypress/screenshots', + trashAssetsBeforeRuns: false, + video: false, + videosFolder: '../../../target/kibana-osquery/cypress/videos', + viewportHeight: 900, + viewportWidth: 1440, + experimentalStudio: true, + + env: { + 'cypress-react-selector': { + root: '#osquery-app', + }, + }, + + e2e: { + baseUrl: 'http://localhost:5601', + setupNodeEvents(on, config) { + // implement node event listeners here + }, + }, +}); diff --git a/x-pack/plugins/osquery/cypress/cypress.json b/x-pack/plugins/osquery/cypress/cypress.json deleted file mode 100644 index 5df26a922d7c3..0000000000000 --- a/x-pack/plugins/osquery/cypress/cypress.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "baseUrl": "http://localhost:5620", - "defaultCommandTimeout": 60000, - "execTimeout": 120000, - "pageLoadTimeout": 12000, - "retries": { - "runMode": 1, - "openMode": 0 - }, - "screenshotsFolder": "../../../target/kibana-osquery/cypress/screenshots", - "trashAssetsBeforeRuns": false, - "video": false, - "videosFolder": "../../../target/kibana-osquery/cypress/videos", - "viewportHeight": 900, - "viewportWidth": 1440, - "experimentalStudio": true, - "env": { - "cypress-react-selector": { - "root": "#osquery-app" - } - } -} diff --git a/x-pack/plugins/osquery/cypress/integration/all/add_integration.spec.ts b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/all/add_integration.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/all/alerts.spec.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/all/alerts.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/all/discover.spec.ts b/x-pack/plugins/osquery/cypress/e2e/all/discover.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/all/discover.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/all/discover.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/all/edit_saved_queries.spec.ts b/x-pack/plugins/osquery/cypress/e2e/all/edit_saved_queries.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/all/edit_saved_queries.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/all/edit_saved_queries.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/all/metrics.spec.ts b/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/all/metrics.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts b/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/all/saved_queries.spec.ts b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/all/saved_queries.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/roles/admin.spec.ts b/x-pack/plugins/osquery/cypress/e2e/roles/admin.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/roles/admin.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/roles/admin.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/roles/alert_test.spec.ts b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/roles/alert_test.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/roles/reader.spec.ts b/x-pack/plugins/osquery/cypress/e2e/roles/reader.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/roles/reader.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/roles/reader.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/roles/t1_analyst.spec.ts b/x-pack/plugins/osquery/cypress/e2e/roles/t1_analyst.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/roles/t1_analyst.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/roles/t1_analyst.cy.ts diff --git a/x-pack/plugins/osquery/cypress/integration/roles/t2_analyst.spec.ts b/x-pack/plugins/osquery/cypress/e2e/roles/t2_analyst.cy.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/integration/roles/t2_analyst.spec.ts rename to x-pack/plugins/osquery/cypress/e2e/roles/t2_analyst.cy.ts diff --git a/x-pack/plugins/osquery/cypress/support/coverage.ts b/x-pack/plugins/osquery/cypress/support/coverage.ts index 65975f65af24f..9278edfcc6ddd 100644 --- a/x-pack/plugins/osquery/cypress/support/coverage.ts +++ b/x-pack/plugins/osquery/cypress/support/coverage.ts @@ -47,7 +47,8 @@ const logMessage = (s: string) => { * If there are more files loaded from support folder, also removes them */ const filterSupportFilesFromCoverage = (totalCoverage: any) => { - const integrationFolder = Cypress.config('integrationFolder'); + // @ts-expect-error update types + const integrationFolder = Cypress.config('e2eFolder'); const supportFile = Cypress.config('supportFile'); /** @type {string} Cypress run-time config has the support folder string */ @@ -64,6 +65,7 @@ const filterSupportFilesFromCoverage = (totalCoverage: any) => { // if we have files from support folder AND the support folder is not same // as the integration, or its prefix (this might remove all app source files) // then remove all files from the support folder + // @ts-expect-error update types if (!integrationFolder.startsWith(supportFolder)) { // remove all covered files from support folder coverage = Cypress._.omitBy(totalCoverage, (fileCoverage, filename) => diff --git a/x-pack/plugins/osquery/cypress/support/index.ts b/x-pack/plugins/osquery/cypress/support/e2e.ts similarity index 100% rename from x-pack/plugins/osquery/cypress/support/index.ts rename to x-pack/plugins/osquery/cypress/support/e2e.ts diff --git a/x-pack/plugins/osquery/cypress/tsconfig.json b/x-pack/plugins/osquery/cypress/tsconfig.json index cbb5b10c48aaf..548ac5dc3eb13 100644 --- a/x-pack/plugins/osquery/cypress/tsconfig.json +++ b/x-pack/plugins/osquery/cypress/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../../../tsconfig.base.json", "include": [ - "**/*" + "**/*", + "../cypress.config.ts" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/osquery/package.json b/x-pack/plugins/osquery/package.json index 8d0e928f72770..fdda0a2316779 100644 --- a/x-pack/plugins/osquery/package.json +++ b/x-pack/plugins/osquery/package.json @@ -5,9 +5,9 @@ "private": true, "license": "Elastic-License", "scripts": { - "cypress:open": "../../../node_modules/.bin/cypress open --config-file ./cypress/cypress.json", + "cypress:open": "../../../node_modules/.bin/cypress open --config-file ./cypress.config.ts", "cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/osquery_cypress/visual_config.ts", - "cypress:run": "../../../node_modules/.bin/cypress run --config-file ./cypress/cypress.json", + "cypress:run": "../../../node_modules/.bin/cypress run --config-file ./cypress.config.ts", "cypress:run-as-ci": "node ../../../scripts/functional_tests --config ../../test/osquery_cypress/cli_config.ts", "nyc": "../../../node_modules/.bin/nyc report --reporter=text-summary" } diff --git a/x-pack/plugins/osquery/tsconfig.json b/x-pack/plugins/osquery/tsconfig.json index 4eac1baa43d79..f9cacf100b691 100644 --- a/x-pack/plugins/osquery/tsconfig.json +++ b/x-pack/plugins/osquery/tsconfig.json @@ -6,6 +6,7 @@ "declaration": true, "declarationMap": true }, + "exclude": ["cypress.config.ts"], "include": [ // add all the folders contains files to be compiled "common/**/*", @@ -13,6 +14,7 @@ "scripts/**/*", "scripts/**/**.json", "server/**/*", + "cypress.config.ts", "../../../typings/**/*", // ECS and Osquery schema files "public/common/schemas/*/**.json", diff --git a/x-pack/plugins/security_solution/cypress/README.md b/x-pack/plugins/security_solution/cypress/README.md index 44f1fa63d732b..5124e8d5c0685 100644 --- a/x-pack/plugins/security_solution/cypress/README.md +++ b/x-pack/plugins/security_solution/cypress/README.md @@ -83,7 +83,7 @@ This configuration runs cypress tests against an arbitrary host. #### integration-test (CI) -This configuration is driven by [elastic/integration-test](https://github.com/elastic/integration-test) which, as part of a bigger set of tests, provisions one VM with two instances configured in CCS mode and runs the [CCS Cypress test specs](./ccs_integration). +This configuration is driven by [elastic/integration-test](https://github.com/elastic/integration-test) which, as part of a bigger set of tests, provisions one VM with two instances configured in CCS mode and runs the [CCS Cypress test specs](./ccs_e2e). The two clusters are named `admin` and `data` and are reachable as follows: @@ -280,13 +280,13 @@ If you are debugging a flaky test, a good tip is to insert a `cy.wait( { deleteRuleFromDetailsPage(); + // @ts-expect-error update types cy.waitFor('@deleteRule').then(() => { cy.get(RULES_TABLE).should('exist'); cy.get(RULES_TABLE) diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule_data_view.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule_data_view.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule_data_view.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule_data_view.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/event_correlation_rule.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/event_correlation_rule.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/export_rule.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/export_rule.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/import_rules.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/import_rules.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/indicator_match_rule.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/indicator_match_rule.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/links.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/links.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/links.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/links.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/machine_learning_rule.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/machine_learning_rule.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/new_terms_rule.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/new_terms_rule.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/prebuilt_rules.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/prebuilt_rules.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/related_integrations.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/related_integrations.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/rules_selection.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/rules_selection.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/rules_table_auto_refresh.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_table_auto_refresh.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/rules_table_auto_refresh.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_table_auto_refresh.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/sorting.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/sorting.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/alerts_table_flow/add_exception.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/exceptions/alerts_table_flow/add_exception.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_flyout.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_flyout.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_flyout.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_flyout.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_management_flow/all_exception_lists_read_only.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/all_exception_lists_read_only.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_management_flow/all_exception_lists_read_only.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/all_exception_lists_read_only.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_table.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_table.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/rule_details_flow/add_exception.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_exception.spec.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/exceptions/rule_details_flow/add_exception.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_exception.spec.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/rule_details_flow/add_exception_data_view.spect.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_exception_data_view.spect.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/exceptions/rule_details_flow/add_exception_data_view.spect.ts rename to x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_exception_data_view.spect.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/rule_details_flow/edit_exception.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/edit_exception.spec.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/exceptions/rule_details_flow/edit_exception.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/edit_exception.spec.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/rule_details_flow/edit_exception_data_view.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/edit_exception_data_view.spec.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/exceptions/rule_details_flow/edit_exception_data_view.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/edit_exception_data_view.spec.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/rule_details_flow/read_only_view.spect.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/read_only_view.spect.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/exceptions/rule_details_flow/read_only_view.spect.ts rename to x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/read_only_view.spect.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/filters/pinned_filters.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/filters/pinned_filters.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/filters/pinned_filters.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/filters/pinned_filters.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/guided_onboarding/tour.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/guided_onboarding/tour.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/guided_onboarding/tour.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/guided_onboarding/tour.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/header/navigation.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/header/navigation.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/header/navigation.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/header/navigation.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/header/search_bar.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/header/search_bar.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/header/search_bar.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/header/search_bar.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/host_details/risk_tab.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/host_details/risk_tab.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/host_details/risk_tab.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/host_details/risk_tab.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/hosts/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/hosts/events_viewer.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/hosts/events_viewer.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/hosts/events_viewer.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/hosts/host_risk_tab.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/hosts/host_risk_tab.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/hosts/hosts_risk_column.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/hosts/hosts_risk_column.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/hosts/hosts_risk_column.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/hosts/hosts_risk_column.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/hosts/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/hosts/inspect.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/hosts/inspect.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/hosts/inspect.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/ml/ml_conditional_links.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/ml/ml_conditional_links.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/network/hover_actions.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/network/hover_actions.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/network/hover_actions.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/network/hover_actions.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/network/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/network/inspect.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/network/inspect.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/network/inspect.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/network/overflow_items.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/network/overflow_items.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/network/overflow_items.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/network/overflow_items.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/overview/cti_link_panel.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/overview/cti_link_panel.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/overview/cti_link_panel.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/overview/cti_link_panel.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/overview/overview.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/overview/overview.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/pagination/pagination.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/pagination/pagination.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/pagination/pagination.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/pagination/pagination.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_templates/creation.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timeline_templates/creation.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timeline_templates/creation.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timeline_templates/creation.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_templates/export.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timeline_templates/export.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timeline_templates/export.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timeline_templates/export.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/creation.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/creation.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/data_providers.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/data_providers.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/export.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/export.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/export.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/export.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/fields_browser.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/fields_browser.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/fields_browser.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/fields_browser.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/flyout_button.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/flyout_button.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/flyout_button.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/flyout_button.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/full_screen.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/full_screen.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/full_screen.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/full_screen.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/inspect.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/inspect.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/inspect.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/local_storage.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/local_storage.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/local_storage.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/local_storage.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/notes_tab.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/notes_tab.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/open_timeline.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/open_timeline.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/overview.tsx b/x-pack/plugins/security_solution/cypress/e2e/timelines/overview.cy.tsx similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/overview.tsx rename to x-pack/plugins/security_solution/cypress/e2e/timelines/overview.cy.tsx diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/pagination.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/pagination.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/pagination.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/pagination.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/query_tab.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/query_tab.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/query_tab.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/query_tab.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/row_renderers.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/row_renderers.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/search_or_filter.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/search_or_filter.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/search_or_filter.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/search_or_filter.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/toggle_column.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/toggle_column.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/toggle_column.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/timelines/toggle_column.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/compatibility.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/urls/compatibility.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/urls/compatibility.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/urls/compatibility.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/not_found.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/urls/not_found.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/urls/not_found.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/urls/not_found.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/users/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/users/inspect.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/users/inspect.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/users/inspect.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/users/user_details.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/users/user_details.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/users/user_details.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/users/user_details.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/users/users_tabs.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/users/users_tabs.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/users/users_tabs.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/users/users_tabs.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/value_lists/value_lists.spec.ts b/x-pack/plugins/security_solution/cypress/e2e/value_lists/value_lists.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/value_lists/value_lists.spec.ts rename to x-pack/plugins/security_solution/cypress/e2e/value_lists/value_lists.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/support/index.js b/x-pack/plugins/security_solution/cypress/support/e2e.js similarity index 100% rename from x-pack/plugins/security_solution/cypress/support/index.js rename to x-pack/plugins/security_solution/cypress/support/e2e.js diff --git a/x-pack/plugins/security_solution/cypress/upgrade_integration/detections/detection_rules/custom_query_rule.spec.ts b/x-pack/plugins/security_solution/cypress/upgrade_e2e/detections/detection_rules/custom_query_rule.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/upgrade_integration/detections/detection_rules/custom_query_rule.spec.ts rename to x-pack/plugins/security_solution/cypress/upgrade_e2e/detections/detection_rules/custom_query_rule.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/upgrade_integration/detections/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/upgrade_e2e/detections/detection_rules/threshold_rule.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/upgrade_integration/detections/detection_rules/threshold_rule.spec.ts rename to x-pack/plugins/security_solution/cypress/upgrade_e2e/detections/detection_rules/threshold_rule.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/cases/import_case.spec.ts b/x-pack/plugins/security_solution/cypress/upgrade_e2e/threat_hunting/cases/import_case.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/cases/import_case.spec.ts rename to x-pack/plugins/security_solution/cypress/upgrade_e2e/threat_hunting/cases/import_case.cy.ts diff --git a/x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/timeline/import_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/upgrade_e2e/threat_hunting/timeline/import_timeline.cy.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/timeline/import_timeline.spec.ts rename to x-pack/plugins/security_solution/cypress/upgrade_e2e/threat_hunting/timeline/import_timeline.cy.ts diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json index 92a934f2dd674..987bcdbe27193 100644 --- a/x-pack/plugins/security_solution/package.json +++ b/x-pack/plugins/security_solution/package.json @@ -8,21 +8,20 @@ "extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js && node ../../../scripts/eslint ./public/detections/mitre/mitre_tactics_techniques.ts --fix", "build-beat-doc": "node scripts/beat_docs/build.js && node ../../../scripts/eslint ../timelines/server/utils/beat_schema/fields.ts --fix", "cypress": "../../../node_modules/.bin/cypress", - "cypress:open": "yarn cypress open --config-file ./cypress/cypress.json", - "cypress:open:ccs": "yarn cypress:open --config integrationFolder=./cypress/ccs_integration", + "cypress:open": "yarn cypress open --config-file ./cypress/cypress.config.ts", + "cypress:open:ccs": "yarn cypress:open --config specPattern=./cypress/ccs_e2e/**/*.cy.ts", "cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/security_solution_cypress/visual_config.ts", - "cypress:open:upgrade": "yarn cypress:open --config integrationFolder=./cypress/upgrade_integration", - "cypress:run": "yarn cypress:run:reporter --browser chrome --spec './cypress/integration/**/*.spec.ts'; status=$?; yarn junit:merge && exit $status", - "cypress:run:spec": "yarn cypress:run:reporter --browser chrome --spec ${SPEC_LIST:-'./cypress/integration/**/*.spec.ts'}; status=$?; yarn junit:merge && exit $status", - "cypress:run:cases": "yarn cypress:run:reporter --browser chrome --spec './cypress/integration/cases/*.spec.ts'; status=$?; yarn junit:merge && exit $status", - "cypress:run:firefox": "yarn cypress:run:reporter --browser firefox --spec './cypress/integration/**/*.spec.ts'; status=$?; yarn junit:merge && exit $status", - "cypress:run:reporter": "yarn cypress run --config-file ./cypress/cypress_ci.json --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json", - "cypress:run:respops": "yarn cypress:run:reporter --browser chrome --spec ./cypress/integration/detection_alerts/*.spec.ts,./cypress/integration/detection_rules/*.spec.ts,./cypress/integration/exceptions/*.spec.ts; status=$?; yarn junit:merge && exit $status", - "cypress:run:ccs": "yarn cypress:run:reporter --browser chrome --config integrationFolder=./cypress/ccs_integration; status=$?; yarn junit:merge && exit $status", + "cypress:open:upgrade": "yarn cypress:open --config specPattern=./cypress/upgrade_e2e/**/*.cy.ts", + "cypress:run": "yarn cypress:run:reporter --browser chrome --spec './cypress/e2e/**/*.cy.ts'; status=$?; yarn junit:merge && exit $status", + "cypress:run:spec": "yarn cypress:run:reporter --browser chrome --spec ${SPEC_LIST:-'./cypress/e2e/**/*.cy.ts'}; status=$?; yarn junit:merge && exit $status", + "cypress:run:cases": "yarn cypress:run:reporter --browser chrome --spec './cypress/e2e/cases/*.cy.ts'; status=$?; yarn junit:merge && exit $status", + "cypress:run:firefox": "yarn cypress:run:reporter --browser firefox --spec './cypress/e2e/**/*.cy.ts'; status=$?; yarn junit:merge && exit $status", + "cypress:run:reporter": "yarn cypress run --config-file ./cypress/cypress_ci.config.ts --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json", + "cypress:run:respops": "yarn cypress:run:reporter --browser chrome --spec ./cypress/e2e/detection_alerts/*.cy.ts,./cypress/e2e/detection_rules/*.cy.ts,./cypress/e2e/exceptions/*.cy.ts; status=$?; yarn junit:merge && exit $status", + "cypress:run:ccs": "yarn cypress:run:reporter --browser chrome --config specPattern=./cypress/ccs_e2e/**/*.cy.ts; status=$?; yarn junit:merge && exit $status", "cypress:run-as-ci": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/cli_config_parallel.ts", "cypress:run-as-ci:firefox": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/config.firefox.ts", - "cypress:run:upgrade": "yarn cypress:run:reporter --browser chrome --config integrationFolder=./cypress/upgrade_integration", - "cypress:run:upgrade:old": "yarn cypress:run:reporter --browser chrome --config integrationFolder=./cypress/upgrade_integration --spec ./cypress/upgrade_integration/threat_hunting/**/*.spec.ts,./cypress/upgrade_integration/detections/**/custom_query_rule.spec.ts; status=$?; yarn junit:merge && exit $status", + "cypress:run:upgrade": "yarn cypress:run:reporter --browser chrome --config specPattern=./cypress/upgrade_e2e/**/*.cy.ts", "junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results && mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/", "test:generate": "node scripts/endpoint/resolver_generator" } diff --git a/x-pack/plugins/threat_intelligence/cypress/cypress.config.ts b/x-pack/plugins/threat_intelligence/cypress/cypress.config.ts new file mode 100644 index 0000000000000..540485920c2ff --- /dev/null +++ b/x-pack/plugins/threat_intelligence/cypress/cypress.config.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'cypress'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig({ + defaultCommandTimeout: 120000, + execTimeout: 120000, + pageLoadTimeout: 120000, + retries: { + runMode: 2, + }, + screenshotsFolder: '../../../target/kibana-threat-intelligence/cypress/screenshots', + trashAssetsBeforeRuns: false, + video: false, + videosFolder: '../../../target/kibana-threat-intelligence/cypress/videos', + viewportHeight: 946, + viewportWidth: 1680, + env: { + protocol: 'http', + hostname: 'localhost', + configport: '5601', + }, + e2e: { + baseUrl: 'http://localhost:5601', + setupNodeEvents(on, config) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('./plugins')(on, config); + }, + }, +}); diff --git a/x-pack/plugins/threat_intelligence/cypress/cypress.json b/x-pack/plugins/threat_intelligence/cypress/cypress.json deleted file mode 100644 index 90b6cdb52e3c1..0000000000000 --- a/x-pack/plugins/threat_intelligence/cypress/cypress.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "baseUrl": "http://localhost:5601", - "defaultCommandTimeout": 120000, - "execTimeout": 120000, - "pageLoadTimeout": 120000, - "retries": { - "runMode": 2 - }, - "screenshotsFolder": "../../../target/kibana-threat-intelligence/cypress/screenshots", - "trashAssetsBeforeRuns": false, - "video": false, - "videosFolder": "../../../target/kibana-threat-intelligence/cypress/videos", - "viewportHeight": 946, - "viewportWidth": 1680, - "env": { - "protocol": "http", - "hostname": "localhost", - "configport": "5601" - } -} diff --git a/x-pack/plugins/threat_intelligence/cypress/integration/empty_page.spec.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/empty_page.cy.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/cypress/integration/empty_page.spec.ts rename to x-pack/plugins/threat_intelligence/cypress/e2e/empty_page.cy.ts diff --git a/x-pack/plugins/threat_intelligence/cypress/integration/indicators.spec.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/cypress/integration/indicators.spec.ts rename to x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts diff --git a/x-pack/plugins/threat_intelligence/cypress/integration/query_bar.spec.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/cypress/integration/query_bar.spec.ts rename to x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts diff --git a/x-pack/plugins/threat_intelligence/cypress/integration/timeline.spec.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/cypress/integration/timeline.spec.ts rename to x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts diff --git a/x-pack/plugins/threat_intelligence/cypress/support/index.js b/x-pack/plugins/threat_intelligence/cypress/support/e2e.js similarity index 100% rename from x-pack/plugins/threat_intelligence/cypress/support/index.js rename to x-pack/plugins/threat_intelligence/cypress/support/e2e.js diff --git a/x-pack/plugins/threat_intelligence/package.json b/x-pack/plugins/threat_intelligence/package.json index 1af856fcdf616..ebbe810a6629c 100644 --- a/x-pack/plugins/threat_intelligence/package.json +++ b/x-pack/plugins/threat_intelligence/package.json @@ -3,21 +3,16 @@ "name": "threat_intelligence", "scripts": { "cypress": "../../../node_modules/.bin/cypress", - "cypress:open": "yarn cypress open --config-file ./cypress/cypress.json", - "cypress:open:ccs": "yarn cypress:open --config integrationFolder=./cypress/ccs_integration", + "cypress:open": "yarn cypress open --config-file ./cypress/cypress.config.ts", "cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/threat_intelligence_cypress/visual_config.ts", - "cypress:open:upgrade": "yarn cypress:open --config integrationFolder=./cypress/upgrade_integration", - "cypress:run": "yarn cypress:run:reporter --browser chrome --spec './cypress/integration/**/*.spec.ts'; status=$?; yarn junit:merge && exit $status", - "cypress:run:spec": "yarn cypress:run:reporter --browser chrome --spec ${SPEC_LIST:-'./cypress/integration/**/*.spec.ts'}; status=$?; yarn junit:merge && exit $status", - "cypress:run:cases": "yarn cypress:run:reporter --browser chrome --spec './cypress/integration/cases/*.spec.ts'; status=$?; yarn junit:merge && exit $status", - "cypress:run:firefox": "yarn cypress:run:reporter --browser firefox --spec './cypress/integration/**/*.spec.ts'; status=$?; yarn junit:merge && exit $status", - "cypress:run:reporter": "yarn cypress run --config-file ./cypress/cypress.json --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json", - "cypress:run:respops": "yarn cypress:run:reporter --browser chrome --spec ./cypress/integration/detection_alerts/*.spec.ts,./cypress/integration/detection_rules/*.spec.ts,./cypress/integration/exceptions/*.spec.ts; status=$?; yarn junit:merge && exit $status", - "cypress:run:ccs": "yarn cypress:run:reporter --browser chrome --config integrationFolder=./cypress/ccs_integration; status=$?; yarn junit:merge && exit $status", + "cypress:run": "yarn cypress:run:reporter --browser chrome --spec './cypress/e2e/**/*.cy.ts'; status=$?; yarn junit:merge && exit $status", + "cypress:run:spec": "yarn cypress:run:reporter --browser chrome --spec ${SPEC_LIST:-'./cypress/e2e/**/*.cy.ts'}; status=$?; yarn junit:merge && exit $status", + "cypress:run:cases": "yarn cypress:run:reporter --browser chrome --spec './cypress/e2e/cases/*.cy.ts'; status=$?; yarn junit:merge && exit $status", + "cypress:run:firefox": "yarn cypress:run:reporter --browser firefox --spec './cypress/e2e/**/*.cy.ts'; status=$?; yarn junit:merge && exit $status", + "cypress:run:reporter": "yarn cypress run --config-file ./cypress/cypress.config.ts --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json", + "cypress:run:respops": "yarn cypress:run:reporter --browser chrome --spec ./cypress/e2e/detection_alerts/*.cy.ts,./cypress/e2e/detection_rules/*.cy.ts,./cypress/e2e/exceptions/*.cy.ts; status=$?; yarn junit:merge && exit $status", "cypress:run-as-ci": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/threat_intelligence_cypress/cli_config_parallel.ts", "cypress:run-as-ci:firefox": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/threat_intelligence_cypress/config.firefox.ts", - "cypress:run:upgrade": "yarn cypress:run:reporter --browser chrome --config integrationFolder=./cypress/upgrade_integration", - "cypress:run:upgrade:old": "yarn cypress:run:reporter --browser chrome --config integrationFolder=./cypress/upgrade_integration --spec ./cypress/upgrade_integration/threat_hunting/**/*.spec.ts,./cypress/upgrade_integration/detections/**/custom_query_rule.spec.ts; status=$?; yarn junit:merge && exit $status", "junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-threat-intelligence/cypress/results/mochawesome*.json > ../../../target/kibana-threat-intelligence/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-threat-intelligence/cypress/results/output.json --reportDir ../../../target/kibana-threat-intelligence/cypress/results && mkdir -p ../../../target/junit && cp ../../../target/kibana-threat-intelligence/cypress/results/*.xml ../../../target/junit/" } } diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index 79e202046a4e0..08dc0f949e01c 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -17,10 +17,7 @@ import semver from 'semver'; import { FtrProviderContext } from './ftr_provider_context'; const retrieveIntegrations = (chunksTotal: number, chunkIndex: number) => { - const pattern = resolve( - __dirname, - '../../plugins/security_solution/cypress/integration/**/*.spec.ts' - ); + const pattern = resolve(__dirname, '../../plugins/security_solution/cypress/e2e/**/*.cy.ts'); const integrationsPaths = globby.sync(pattern); const chunkSize = Math.ceil(integrationsPaths.length / chunksTotal); diff --git a/x-pack/test/threat_intelligence_cypress/runner.ts b/x-pack/test/threat_intelligence_cypress/runner.ts index c165cb552b9ba..5ab9d032c55f2 100644 --- a/x-pack/test/threat_intelligence_cypress/runner.ts +++ b/x-pack/test/threat_intelligence_cypress/runner.ts @@ -22,10 +22,7 @@ import { tiAbusechMalwareBazaar } from './pipelines/ti_abusech_malware_bazaar'; import { tiAbusechUrl } from './pipelines/ti_abusech_url'; const retrieveIntegrations = (chunksTotal: number, chunkIndex: number) => { - const pattern = resolve( - __dirname, - '../../plugins/threat_intelligence/cypress/integration/**/*.spec.ts' - ); + const pattern = resolve(__dirname, '../../plugins/threat_intelligence/cypress/e2e/**/*.cy.ts'); const integrationsPaths = globby.sync(pattern); const chunkSize = Math.ceil(integrationsPaths.length / chunksTotal); diff --git a/yarn.lock b/yarn.lock index 1a56456289f13..ca0826165cf2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -108,7 +108,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.19.0", "@babel/core@^7.7.5": +"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.19.0", "@babel/core@^7.7.5": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== @@ -414,7 +414,7 @@ "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-class-properties@^7.12.1", "@babel/plugin-proposal-class-properties@^7.16.0", "@babel/plugin-proposal-class-properties@^7.18.6": +"@babel/plugin-proposal-class-properties@^7.12.1", "@babel/plugin-proposal-class-properties@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== @@ -505,7 +505,7 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.12.1" -"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.16.0", "@babel/plugin-proposal-object-rest-spread@^7.18.9": +"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7" integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== @@ -963,7 +963,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-runtime@^7.16.0", "@babel/plugin-transform-runtime@^7.18.10": +"@babel/plugin-transform-runtime@^7.18.10": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f" integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ== @@ -1035,7 +1035,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/preset-env@^7.12.11", "@babel/preset-env@^7.16.0", "@babel/preset-env@^7.19.0": +"@babel/preset-env@^7.12.11", "@babel/preset-env@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.0.tgz#fd18caf499a67d6411b9ded68dc70d01ed1e5da7" integrity sha512-1YUju1TAFuzjIQqNM9WsF4U6VbD/8t3wEAlw3LFYuuEr+ywqLRcSXxFKz4DCEj+sN94l/XTDiUXYRrsvMpz9WQ== @@ -1135,7 +1135,7 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.12.10", "@babel/preset-react@^7.16.0", "@babel/preset-react@^7.18.6": +"@babel/preset-react@^7.12.10", "@babel/preset-react@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== @@ -1175,7 +1175,7 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.0", "@babel/runtime@^7.19.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.19.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== @@ -1309,39 +1309,15 @@ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.1.tgz#b6b8d81780b9a9f6459f4bfe9226ac6aefaefe87" integrity sha512-aG20vknL4/YjQF9BSV7ts4EWm/yrjagAN7OWBNmlbEOUiu0llj4OGrFoOKK3g2vey4/p2omKCoHrWtPxSwV3HA== -"@cypress/browserify-preprocessor@3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@cypress/browserify-preprocessor/-/browserify-preprocessor-3.0.2.tgz#1dbecae394937aed47a3524cad47086c2ded8c50" - integrity sha512-y6mlFR+IR2cqcm3HabSp7AEcX9QfF1EUL4eOaw/7xexdhmdQU8ez6piyRopZQob4BK8oKTsc9PkupsU2rzjqMA== - dependencies: - "@babel/core" "^7.16.0" - "@babel/plugin-proposal-class-properties" "^7.16.0" - "@babel/plugin-proposal-object-rest-spread" "^7.16.0" - "@babel/plugin-transform-runtime" "^7.16.0" - "@babel/preset-env" "^7.16.0" - "@babel/preset-react" "^7.16.0" - "@babel/runtime" "^7.16.0" - babel-plugin-add-module-exports "^1.0.4" - babelify "^10.0.0" - bluebird "^3.7.2" - browserify "^16.2.3" - coffeeify "^3.0.1" - coffeescript "^1.12.7" - debug "^4.3.2" - fs-extra "^9.0.0" - lodash.clonedeep "^4.5.0" - through2 "^2.0.0" - watchify "^4.0.0" - -"@cypress/code-coverage@^3.9.12": - version "3.9.12" - resolved "https://registry.yarnpkg.com/@cypress/code-coverage/-/code-coverage-3.9.12.tgz#f1eab362a71734f997dfb870342cecff20dae23d" - integrity sha512-2QuDSQ2ovz2ZsbQImM917q+9JmEq4afC4kpgHe2o3rTQxUrs7CdHM84rT8XKl0gJIXmbMcNq2rZqe40/eFmCFw== +"@cypress/code-coverage@^3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@cypress/code-coverage/-/code-coverage-3.10.0.tgz#2132dbb7ae068cab91790926d50a9bf85140cab4" + integrity sha512-K5pW2KPpK4vKMXqxd6vuzo6m9BNgpAv1LcrrtmqAtOJ1RGoEILXYZVost0L6Q+V01NyY7n7jXIIfS7LR3nP6YA== dependencies: - "@cypress/browserify-preprocessor" "3.0.2" + "@cypress/webpack-preprocessor" "^5.11.0" chalk "4.1.2" dayjs "1.10.7" - debug "4.3.3" + debug "4.3.4" execa "4.1.0" globby "11.0.4" istanbul-lib-coverage "3.0.0" @@ -1386,13 +1362,13 @@ snap-shot-compare "2.8.3" snap-shot-store "1.2.3" -"@cypress/webpack-preprocessor@^5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.6.0.tgz#9648ae22d2e52f17a604e2a493af27a9c96568bd" - integrity sha512-kSelTDe6gs3Skp4vPP2vfTvAl+Ua+9rR/AMTir7bgJihDvzFESqnjWtF6N1TrPo+vCFVGx0VUA6JUvDkhvpwhA== +"@cypress/webpack-preprocessor@^5.11.0", "@cypress/webpack-preprocessor@^5.12.2": + version "5.12.2" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.12.2.tgz#9cc623a5629980d7f2619569bffc8e3f05a701ae" + integrity sha512-t29wEFvI87IMnCd8taRunwStNsFjFWg138fGF0hPQOYgSj30fbzCEwFD9cAQLYMMcjjuXcnnw8yOfkzIZBBNVQ== dependencies: - bluebird "^3.7.1" - debug "4.3.2" + bluebird "3.7.1" + debug "^4.3.2" lodash "^4.17.20" "@cypress/xvfb@^1.2.4": @@ -5982,14 +5958,7 @@ "@types/babel__template" "*" "@types/babel__traverse" "*" -"@types/babel__generator@*": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc" - integrity sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__generator@^7.6.4": +"@types/babel__generator@*", "@types/babel__generator@^7.6.4": version "7.6.4" resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== @@ -9104,7 +9073,7 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== -JSONStream@1.3.5, JSONStream@^1.0.3: +JSONStream@1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== @@ -9150,7 +9119,7 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== -acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2, acorn-node@^1.6.1: +acorn-node@^1.3.0, acorn-node@^1.6.1: version "1.8.2" resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== @@ -9453,7 +9422,7 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.0, anymatch@^3.0.3, anymatch@^3.1.0, anymatch@~3.1.2: +anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -9781,7 +9750,7 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assert@^1.1.1, assert@^1.4.0: +assert@^1.1.1: version "1.5.0" resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== @@ -10216,11 +10185,6 @@ babel-runtime@6.x, babel-runtime@^6.26.0: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babelify@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/babelify/-/babelify-10.0.0.tgz#fe73b1a22583f06680d8d072e25a1e0d1d1d7fb5" - integrity sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg== - backport@^8.9.2: version "8.9.2" resolved "https://registry.yarnpkg.com/backport/-/backport-8.9.2.tgz#cf0ec69428f9e86c20e1898dd77e8f6c12bf5afa" @@ -10375,7 +10339,12 @@ blob-util@^2.0.2: resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== -bluebird@3.7.2, bluebird@^3.3.5, bluebird@^3.5.5, bluebird@^3.7.1, bluebird@^3.7.2: +bluebird@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de" + integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg== + +bluebird@3.7.2, bluebird@^3.3.5, bluebird@^3.5.5, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -10505,18 +10474,6 @@ brotli@^1.2.0: dependencies: base64-js "^1.1.2" -browser-pack@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.1.0.tgz#c34ba10d0b9ce162b5af227c7131c92c2ecd5774" - integrity sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA== - dependencies: - JSONStream "^1.0.3" - combine-source-map "~0.8.0" - defined "^1.0.0" - safe-buffer "^5.1.1" - through2 "^2.0.0" - umd "^3.0.0" - browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -10529,13 +10486,6 @@ browser-resolve@^1.8.1: dependencies: resolve "1.1.7" -browser-resolve@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-2.0.0.tgz#99b7304cb392f8d73dba741bb2d7da28c6d7842b" - integrity sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ== - dependencies: - resolve "^1.17.0" - browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" @@ -10601,121 +10551,13 @@ browserify-sign@^4.0.0: inherits "^2.0.1" parse-asn1 "^5.0.0" -browserify-zlib@^0.2.0, browserify-zlib@~0.2.0: +browserify-zlib@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== dependencies: pako "~1.0.5" -browserify@^16.2.3: - version "16.5.2" - resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.2.tgz#d926835e9280fa5fd57f5bc301f2ef24a972ddfe" - integrity sha512-TkOR1cQGdmXU9zW4YukWzWVSJwrxmNdADFbqbE3HFgQWe5wqZmOawqZ7J/8MPCwk/W8yY7Y0h+7mOtcZxLP23g== - dependencies: - JSONStream "^1.0.3" - assert "^1.4.0" - browser-pack "^6.0.1" - browser-resolve "^2.0.0" - browserify-zlib "~0.2.0" - buffer "~5.2.1" - cached-path-relative "^1.0.0" - concat-stream "^1.6.0" - console-browserify "^1.1.0" - constants-browserify "~1.0.0" - crypto-browserify "^3.0.0" - defined "^1.0.0" - deps-sort "^2.0.0" - domain-browser "^1.2.0" - duplexer2 "~0.1.2" - events "^2.0.0" - glob "^7.1.0" - has "^1.0.0" - htmlescape "^1.1.0" - https-browserify "^1.0.0" - inherits "~2.0.1" - insert-module-globals "^7.0.0" - labeled-stream-splicer "^2.0.0" - mkdirp-classic "^0.5.2" - module-deps "^6.2.3" - os-browserify "~0.3.0" - parents "^1.0.1" - path-browserify "~0.0.0" - process "~0.11.0" - punycode "^1.3.2" - querystring-es3 "~0.2.0" - read-only-stream "^2.0.0" - readable-stream "^2.0.2" - resolve "^1.1.4" - shasum "^1.0.0" - shell-quote "^1.6.1" - stream-browserify "^2.0.0" - stream-http "^3.0.0" - string_decoder "^1.1.1" - subarg "^1.0.0" - syntax-error "^1.1.1" - through2 "^2.0.0" - timers-browserify "^1.0.1" - tty-browserify "0.0.1" - url "~0.11.0" - util "~0.10.1" - vm-browserify "^1.0.0" - xtend "^4.0.0" - -browserify@^17.0.0: - version "17.0.0" - resolved "https://registry.yarnpkg.com/browserify/-/browserify-17.0.0.tgz#4c48fed6c02bfa2b51fd3b670fddb805723cdc22" - integrity sha512-SaHqzhku9v/j6XsQMRxPyBrSP3gnwmE27gLJYZgMT2GeK3J0+0toN+MnuNYDfHwVGQfLiMZ7KSNSIXHemy905w== - dependencies: - JSONStream "^1.0.3" - assert "^1.4.0" - browser-pack "^6.0.1" - browser-resolve "^2.0.0" - browserify-zlib "~0.2.0" - buffer "~5.2.1" - cached-path-relative "^1.0.0" - concat-stream "^1.6.0" - console-browserify "^1.1.0" - constants-browserify "~1.0.0" - crypto-browserify "^3.0.0" - defined "^1.0.0" - deps-sort "^2.0.1" - domain-browser "^1.2.0" - duplexer2 "~0.1.2" - events "^3.0.0" - glob "^7.1.0" - has "^1.0.0" - htmlescape "^1.1.0" - https-browserify "^1.0.0" - inherits "~2.0.1" - insert-module-globals "^7.2.1" - labeled-stream-splicer "^2.0.0" - mkdirp-classic "^0.5.2" - module-deps "^6.2.3" - os-browserify "~0.3.0" - parents "^1.0.1" - path-browserify "^1.0.0" - process "~0.11.0" - punycode "^1.3.2" - querystring-es3 "~0.2.0" - read-only-stream "^2.0.0" - readable-stream "^2.0.2" - resolve "^1.1.4" - shasum-object "^1.0.0" - shell-quote "^1.6.1" - stream-browserify "^3.0.0" - stream-http "^3.0.0" - string_decoder "^1.1.1" - subarg "^1.0.0" - syntax-error "^1.1.1" - through2 "^2.0.0" - timers-browserify "^1.0.1" - tty-browserify "0.0.1" - url "~0.11.0" - util "~0.12.0" - vm-browserify "^1.0.0" - xtend "^4.0.0" - browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.6, browserslist@^4.20.2, browserslist@^4.20.3: version "4.21.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.0.tgz#7ab19572361a140ecd1e023e2c1ed95edda0cefe" @@ -10793,14 +10635,6 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -buffer@~5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" - integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -10912,11 +10746,6 @@ cacheable-request@^7.0.2: normalize-url "^6.0.1" responselike "^2.0.0" -cached-path-relative@^1.0.0, cached-path-relative@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db" - integrity sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg== - cachedir@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" @@ -11472,19 +11301,6 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -coffeeify@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/coffeeify/-/coffeeify-3.0.1.tgz#5e2753000c50bd24c693115f33864248dd11136c" - integrity sha512-Qjnr7UX6ldK1PHV7wCnv7AuCd4q19KTUtwJnu/6JRJB4rfm12zvcXtKdacUoePOKr1I4ka/ydKiwWpNAdsQb0g== - dependencies: - convert-source-map "^1.3.0" - through2 "^2.0.0" - -coffeescript@^1.12.7: - version "1.12.7" - resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27" - integrity sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA== - collapse-white-space@^1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" @@ -11584,16 +11400,6 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -combine-source-map@^0.8.0, combine-source-map@~0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b" - integrity sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos= - dependencies: - convert-source-map "~1.1.0" - inline-source-map "~0.6.0" - lodash.memoize "~3.0.3" - source-map "~0.5.3" - combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz" @@ -11722,7 +11528,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@~1.6.0: +concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@~1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -11798,7 +11604,7 @@ console-log-level@^1.4.1: resolved "https://registry.yarnpkg.com/console-log-level/-/console-log-level-1.4.1.tgz#9c5a6bb9ef1ef65b05aba83028b0ff894cdf630a" integrity sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ== -constants-browserify@^1.0.0, constants-browserify@~1.0.0: +constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= @@ -11832,18 +11638,13 @@ contentstream@^1.0.0: dependencies: readable-stream "~1.0.33-1" -convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.3.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== dependencies: safe-buffer "~5.1.1" -convert-source-map@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" - integrity sha1-SCnId+n+SbMWHzvzZziI4gRpmGA= - cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -12082,7 +11883,7 @@ crypt@~0.0.1: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= -crypto-browserify@^3.0.0, crypto-browserify@^3.11.0: +crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== @@ -12350,20 +12151,20 @@ cyclist@~0.2.2: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= -cypress-axe@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-0.14.0.tgz#5f5e70fb36b8cb3ba73a8ba01e9262ff1268d5e2" - integrity sha512-7Rdjnko0MjggCmndc1wECAkvQBIhuy+DRtjF7bd5YPZRFvubfMNvrxfqD8PWQmxm7MZE0ffS4Xr43V6ZmvLopg== +cypress-axe@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.0.0.tgz#ab4e9486eaa3bb956a90a1ae40d52df42827b4f0" + integrity sha512-QBlNMAd5eZoyhG8RGGR/pLtpHGkvgWXm2tkP68scJ+AjYiNNOlJihxoEwH93RT+rWOLrefw4iWwEx8kpEcrvJA== cypress-file-upload@^5.0.8: version "5.0.8" resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1" integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g== -cypress-multi-reporters@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-1.6.0.tgz#2c6833b92e3df412c233657c55009e2d5e1cc7c1" - integrity sha512-JN9yMzDmPwwirzi95N2FC8VJZ0qp+uUJ1ixYHpJFaAtGgIx15LjVmASqQaxnDh8q57jIIJ6C0o7imiLU6N1YNQ== +cypress-multi-reporters@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-1.6.1.tgz#515b891f6c80e0700068efb03ab9d55388399c95" + integrity sha512-FPeC0xWF1N6Myrwc2m7KC0xxlrtG8+x4hlsPFBDRWP8u/veR2x90pGaH3BuJfweV7xoQ4Zo85Qjhu3fgZGrBQQ== dependencies: debug "^4.1.1" lodash "^4.17.15" @@ -12373,27 +12174,27 @@ cypress-pipe@^2.0.0: resolved "https://registry.yarnpkg.com/cypress-pipe/-/cypress-pipe-2.0.0.tgz#577df7a70a8603d89a96dfe4092a605962181af8" integrity sha512-KW9s+bz4tFLucH3rBGfjW+Q12n7S4QpUSSyxiGrgPOfoHlbYWzAGB3H26MO0VTojqf9NVvfd5Kt0MH5XMgbfyg== -cypress-react-selector@^2.3.17: - version "2.3.17" - resolved "https://registry.yarnpkg.com/cypress-react-selector/-/cypress-react-selector-2.3.17.tgz#010382b486c4ec342ab61bcd121bb4ccd79e4590" - integrity sha512-yBi3wv5XUuzcYXearR0PN8lz0kMk44TOP4LVhwK1e4IU1fuWtlyVag1i+2eg0sdKRJ8VePEgmJhgZfOgd3VEgg== +cypress-react-selector@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cypress-react-selector/-/cypress-react-selector-3.0.0.tgz#e86018fffea07ba40c7a1f467a89b475a83cbcae" + integrity sha512-AQCgwbcMDkIdYcf6knvLxqzBnejahIbJPHqUhARi8k+QbM8sgUBDds98PaHJVMdPiX2J8RJjXHmUMPD8VerPSw== dependencies: resq "1.10.2" -cypress-real-events@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.0.tgz#ad6a78de33af3af0e6437f5c713e30691c44472c" - integrity sha512-iyXp07j0V9sG3YClVDcvHN2DAQDgr+EjTID82uWDw6OZBlU3pXEBqTMNYqroz3bxlb0k+F74U81aZwzMNaKyew== +cypress-real-events@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.1.tgz#8f430d67c29ea4f05b9c5b0311780120cbc9b935" + integrity sha512-/Bg15RgJ0SYsuXc6lPqH08x19z6j2vmhWN4wXfJqm3z8BTAFiK2MvipZPzxT8Z0jJP0q7kuniWrLIvz/i/8lCQ== -cypress-recurse@^1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/cypress-recurse/-/cypress-recurse-1.20.0.tgz#66c09d876ce1c143daa62fea222b5b80067a7fc9" - integrity sha512-8/gqot/XnVkSF8ssgn3zLRTfPw7Bum2tMIOxf6NO+Wqk0MBQdd4NPNVCObllZmmviLsGmF6ZXwlbXZ8TYvD6dw== +cypress-recurse@^1.23.0: + version "1.23.0" + resolved "https://registry.yarnpkg.com/cypress-recurse/-/cypress-recurse-1.23.0.tgz#f87334747516de6737bc4708754e8f429057bc6d" + integrity sha512-CAsdvynhuR3SUEXVJRO2jBEnZRJ6nJp7nMXHwzV4UQq9Lap3Bj72AwcJK0cl51fJXcTaGDXYTQQ9zvGe3TyaQA== -cypress@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.6.1.tgz#a7d6b5a53325b3dc4960181f5800a5ade0f085eb" - integrity sha512-ECzmV7pJSkk+NuAhEw6C3D+RIRATkSb2VAHXDY6qGZbca/F9mv5pPsj2LO6Ty6oIFVBTrwCyL9agl28MtJMe2g== +cypress@^10.7.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.7.0.tgz#2d37f8b9751c6de33ee48639cb7e67a2ce593231" + integrity sha512-gTFvjrUoBnqPPOu9Vl5SBHuFlzx/Wxg/ZXIz2H4lzoOLFelKeF7mbwYUOzgzgF0oieU2WhJAestQdkgwJMMTvQ== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" @@ -12930,20 +12731,6 @@ debug@4.3.1: dependencies: ms "2.1.2" -debug@4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - -debug@4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== - dependencies: - ms "2.1.2" - debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -13211,16 +12998,6 @@ deprecation@^2.0.0, deprecation@^2.3.1: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== -deps-sort@^2.0.0, deps-sort@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.1.tgz#9dfdc876d2bcec3386b6829ac52162cda9fa208d" - integrity sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw== - dependencies: - JSONStream "^1.0.3" - shasum-object "^1.0.0" - subarg "^1.0.0" - through2 "^2.0.0" - des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -13279,7 +13056,7 @@ detect-port@^1.3.0: address "^1.0.1" debug "^2.6.0" -detective@^5.0.2, detective@^5.2.0: +detective@^5.0.2: version "5.2.0" resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== @@ -13455,7 +13232,7 @@ dom-walk@^0.1.0: resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg= -domain-browser@^1.1.1, domain-browser@^1.2.0: +domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== @@ -13595,7 +13372,7 @@ dpdm@3.5.0: typescript "^3.5.3" yargs "^13.3.0" -duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2, duplexer2@~0.1.4: +duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= @@ -14556,11 +14333,6 @@ events@^1.0.2: resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= -events@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5" - integrity sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg== - events@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -15573,7 +15345,7 @@ geojson-vt@^3.2.1: resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7" integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg== -get-assigned-identifiers@^1.1.0, get-assigned-identifiers@^1.2.0: +get-assigned-identifiers@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1" integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ== @@ -15795,7 +15567,7 @@ glob@^6.0.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0, glob@~7.2.0: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0, glob@~7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -16568,11 +16340,6 @@ html@1.0.0: dependencies: concat-stream "^1.4.7" -htmlescape@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" - integrity sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E= - htmlparser2@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78" @@ -16881,7 +16648,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -16906,13 +16673,6 @@ ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== -inline-source-map@~0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5" - integrity sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU= - dependencies: - source-map "~0.5.3" - inline-style-parser@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" @@ -16966,22 +16726,6 @@ inquirer@^8.2.3: through "^2.3.6" wrap-ansi "^7.0.0" -insert-module-globals@^7.0.0, insert-module-globals@^7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.2.1.tgz#d5e33185181a4e1f33b15f7bf100ee91890d5cb3" - integrity sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg== - dependencies: - JSONStream "^1.0.3" - acorn-node "^1.5.2" - combine-source-map "^0.8.0" - concat-stream "^1.6.1" - is-buffer "^1.1.0" - path-is-absolute "^1.0.1" - process "~0.11.0" - through2 "^2.0.0" - undeclared-identifiers "^1.1.2" - xtend "^4.0.0" - install-artifact-from-github@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.0.tgz#cab6ff821976b8a35b0c079da19a727c90381a40" @@ -17171,7 +16915,7 @@ is-boolean-object@^1.0.1, is-boolean-object@^1.1.0: dependencies: call-bind "^1.0.0" -is-buffer@^1.0.2, is-buffer@^1.1.0, is-buffer@^1.1.5, is-buffer@~1.1.1: +is-buffer@^1.0.2, is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -17313,11 +17057,6 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.0.0.tgz#038c31b774709641bda678b1f06a4e3227c10b3e" integrity sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g== -is-generator-function@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" - integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw== - is-glob@^3.0.0, is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -18586,13 +18325,6 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" -json-stable-stringify@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" - integrity sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U= - dependencies: - jsonify "~0.0.0" - json-stringify-pretty-compact@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.2.0.tgz#0bc316b5e6831c07041fc35612487fb4e9ab98b8" @@ -18844,14 +18576,6 @@ kuler@^2.0.0: resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== -labeled-stream-splicer@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz#42a41a16abcd46fd046306cf4f2c3576fffb1c21" - integrity sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw== - dependencies: - inherits "^2.0.1" - stream-splicer "^2.0.0" - language-subtag-registry@~0.3.2: version "0.3.21" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" @@ -19140,11 +18864,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -19235,11 +18954,6 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.memoize@~3.0.3: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" - integrity sha1-LcvSwofLwKVcxCMovQxzYVDVPj8= - lodash.merge@4.6.2, lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -19986,7 +19700,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== @@ -20173,27 +19887,6 @@ mock-fs@^5.1.2: resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-5.1.2.tgz#6fa486e06d00f8793a8d2228de980eff93ce6db7" integrity sha512-YkjQkdLulFrz0vD4BfNQdQRVmgycXTV7ykuHMlyv+C8WCHazpkiQRDthwa02kSyo8wKnY9wRptHfQLgmf0eR+A== -module-deps@^6.2.3: - version "6.2.3" - resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-6.2.3.tgz#15490bc02af4b56cf62299c7c17cba32d71a96ee" - integrity sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA== - dependencies: - JSONStream "^1.0.3" - browser-resolve "^2.0.0" - cached-path-relative "^1.0.2" - concat-stream "~1.6.0" - defined "^1.0.0" - detective "^5.2.0" - duplexer2 "^0.1.2" - inherits "^2.0.1" - parents "^1.0.0" - readable-stream "^2.0.2" - resolve "^1.4.0" - stream-combiner2 "^1.1.1" - subarg "^1.0.0" - through2 "^2.0.0" - xtend "^4.0.0" - module-details-from-path@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" @@ -21201,7 +20894,7 @@ original-url@^1.2.3: dependencies: forwarded-parse "^2.1.0" -os-browserify@^0.3.0, os-browserify@~0.3.0: +os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= @@ -21244,13 +20937,6 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= -outpipe@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/outpipe/-/outpipe-1.1.1.tgz#50cf8616365e87e031e29a5ec9339a3da4725fa2" - integrity sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I= - dependencies: - shell-quote "^1.4.2" - overlayscrollbars@^1.13.1: version "1.13.1" resolved "https://registry.yarnpkg.com/overlayscrollbars/-/overlayscrollbars-1.13.1.tgz#0b840a88737f43a946b9d87875a2f9e421d0338a" @@ -21462,13 +21148,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parents@^1.0.0, parents@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751" - integrity sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E= - dependencies: - path-platform "~0.11.15" - parse-asn1@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" @@ -21562,12 +21241,12 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -path-browserify@0.0.1, path-browserify@~0.0.0: +path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== -path-browserify@^1.0.0, path-browserify@^1.0.1: +path-browserify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== @@ -21587,7 +21266,7 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: +path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= @@ -21612,11 +21291,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-platform@~0.11.15: - version "0.11.15" - resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" - integrity sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I= - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -22424,7 +22098,7 @@ process-on-spawn@^1.0.0: dependencies: fromentries "^1.2.0" -process@^0.11.10, process@~0.11.0: +process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= @@ -22676,7 +22350,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.2.4, punycode@^1.3.2: +punycode@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -22742,7 +22416,7 @@ query-string@^6.13.2: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" -querystring-es3@^0.2.0, querystring-es3@~0.2.0: +querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= @@ -23543,13 +23217,6 @@ read-installed@~4.0.3: optionalDependencies: graceful-fs "^4.1.2" -read-only-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" - integrity sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A= - dependencies: - readable-stream "^2.0.2" - read-package-json@^2.0.0, read-package-json@^2.0.10: version "2.1.1" resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.1.tgz#16aa66c59e7d4dad6288f179dd9295fd59bb98f1" @@ -23621,7 +23288,7 @@ readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0 isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -24243,7 +23910,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.4, resolve@^1.1.5, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.9.0: +resolve@^1.1.5, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.3.2, resolve@^1.9.0: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -24854,7 +24521,7 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: +sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== @@ -24898,21 +24565,6 @@ sharp@^0.30.1: tar-fs "^2.1.1" tunnel-agent "^0.6.0" -shasum-object@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shasum-object/-/shasum-object-1.0.0.tgz#0b7b74ff5b66ecf9035475522fa05090ac47e29e" - integrity sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg== - dependencies: - fast-safe-stringify "^2.0.7" - -shasum@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" - integrity sha1-5wEjENj0F/TetXEhUOVni4euVl8= - dependencies: - json-stable-stringify "~0.0.0" - sha.js "~2.4.4" - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -24937,11 +24589,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.4.2, shell-quote@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" - integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== - shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -25241,7 +24888,7 @@ source-map@0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= -source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -25568,7 +25215,7 @@ store2@^2.12.0: resolved "https://registry.yarnpkg.com/store2/-/store2-2.12.0.tgz#e1f1b7e1a59b6083b2596a8d067f6ee88fd4d3cf" integrity sha512-7t+/wpKLanLzSnQPX8WAcuLCCeuSHoWdQuh9SB3xD0kNOM38DNf+0Oa+wmvxmYueRzkmh6IcdKFtvTa+ecgPDw== -stream-browserify@^2.0.0, stream-browserify@^2.0.1: +stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== @@ -25576,14 +25223,6 @@ stream-browserify@^2.0.0, stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-browserify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" - integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== - dependencies: - inherits "~2.0.4" - readable-stream "^3.5.0" - stream-chopper@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/stream-chopper/-/stream-chopper-3.0.1.tgz#73791ae7bf954c297d6683aec178648efc61dd75" @@ -25591,14 +25230,6 @@ stream-chopper@^3.0.1: dependencies: readable-stream "^3.0.6" -stream-combiner2@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" - integrity sha1-+02KFCDqNidk4hrUeAOXvry0HL4= - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" - stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -25618,16 +25249,6 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" -stream-http@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.1.1.tgz#0370a8017cf8d050b9a8554afe608f043eaff564" - integrity sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.4" - readable-stream "^3.6.0" - xtend "^4.0.2" - stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" @@ -25638,14 +25259,6 @@ stream-slicer@0.0.6: resolved "https://registry.yarnpkg.com/stream-slicer/-/stream-slicer-0.0.6.tgz#f86b2ac5c2440b7a0a87b71f33665c0788046138" integrity sha1-+GsqxcJEC3oKh7cfM2ZcB4gEYTg= -stream-splicer@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.1.tgz#0b13b7ee2b5ac7e0609a7463d83899589a363fcd" - integrity sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg== - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.2" - stream-to-async-iterator@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/stream-to-async-iterator/-/stream-to-async-iterator-0.2.0.tgz#bef5c885e9524f98b2fa5effecc357bd58483780" @@ -25980,13 +25593,6 @@ stylis@4.0.13: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== -subarg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" - integrity sha1-9izxdYHplrSPyWVpn1TAauJouNI= - dependencies: - minimist "^1.1.0" - success-symbol@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/success-symbol/-/success-symbol-0.1.0.tgz#24022e486f3bf1cdca094283b769c472d3b72897" @@ -26130,13 +25736,6 @@ synchronous-promise@^2.0.15: resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.15.tgz#07ca1822b9de0001f5ff73595f3d08c4f720eb8e" integrity sha512-k8uzYIkIVwmT+TcglpdN50pS2y1BDcUnBPK9iJeGu0Pl1lOI8pD6wtzgw91Pjpe+RxtTncw32tLxs/R0yNL2Mg== -syntax-error@^1.1.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" - integrity sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w== - dependencies: - acorn-node "^1.2.0" - tabbable@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.2.1.tgz#e3fda7367ddbb172dcda9f871c0fdb36d1c4cd9c" @@ -26431,13 +26030,6 @@ timed-out@^2.0.0: resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a" integrity sha1-84sK6B03R9YoAB9B2vxlKs5nHAo= -timers-browserify@^1.0.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" - integrity sha1-ycWLV1voQHN1y14kYtrO50NZ9B0= - dependencies: - process "~0.11.0" - timers-browserify@^2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.6.tgz#241e76927d9ca05f4d959819022f5b3664b64bae" @@ -26834,11 +26426,6 @@ tty-browserify@0.0.0: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= -tty-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" - integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -26964,11 +26551,6 @@ uglify-js@^3.1.4, uglify-js@^3.14.3: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.4.tgz#68756f17d1b90b9d289341736cb9a567d6882f90" integrity sha512-AbiSR44J0GoCeV81+oxcy/jDOElO2Bx3d0MfQCUShq7JRXaM4KtQopZsq2vFv8bCq2yMaGrw1FgygUd03RyRDA== -umd@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" - integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow== - unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -26992,17 +26574,6 @@ unc-path-regex@^0.1.2: resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= -undeclared-identifiers@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz#9254c1d37bdac0ac2b52de4b6722792d2a91e30f" - integrity sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw== - dependencies: - acorn-node "^1.3.0" - dash-ast "^1.0.0" - get-assigned-identifiers "^1.2.0" - simple-concat "^1.0.0" - xtend "^4.0.1" - undefsafe@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" @@ -27378,7 +26949,7 @@ url-template@^2.0.8: resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE= -url@^0.11.0, url@~0.11.0: +url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= @@ -27491,23 +27062,6 @@ util@^0.11.0: dependencies: inherits "2.0.3" -util@~0.10.1: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" - integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== - dependencies: - inherits "2.0.3" - -util@~0.12.0: - version "0.12.2" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.2.tgz#54adb634c9e7c748707af2bf5a8c7ab640cbba2b" - integrity sha512-XE+MkWQvglYa+IOfBt5UFG93EmncEMP23UqpgDvVZVFBPxwmkK10QRp6pgU4xICPnWRf/t0zPv4noYSUq9gqUQ== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - safe-buffer "^5.1.2" - utila@^0.4.0, utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -28051,7 +27605,7 @@ vinyl@^2.0.0, vinyl@^2.1.0, vinyl@^2.2.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vm-browserify@^1.0.0, vm-browserify@^1.0.1: +vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== @@ -28093,19 +27647,6 @@ warning@^4.0.2: dependencies: loose-envify "^1.0.0" -watchify@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/watchify/-/watchify-4.0.0.tgz#53b002d51e7b0eb640b851bb4de517a689973392" - integrity sha512-2Z04dxwoOeNxa11qzWumBTgSAohTC0+ScuY7XMenPnH+W2lhTcpEOJP4g2EIG/SWeLadPk47x++Yh+8BqPM/lA== - dependencies: - anymatch "^3.1.0" - browserify "^17.0.0" - chokidar "^3.4.0" - defined "^1.0.0" - outpipe "^1.1.0" - through2 "^4.0.2" - xtend "^4.0.2" - watchpack-chokidar2@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" From 698717d7a49be0bec976d05d8df98175b47e9065 Mon Sep 17 00:00:00 2001 From: Byron Hulcher Date: Tue, 13 Sep 2022 12:29:35 -0400 Subject: [PATCH 25/28] Revert "[Enterprise Search] Use connector scheduling for crawler (#140268)" (#140434) This reverts commit ad0b42781b975e38ea3223e785a7d6006c8746ad. Co-authored-by: Dmitriy Burlutskiy --- .../enterprise_search/common/types/indices.ts | 6 +----- .../connector/connector_scheduling.tsx | 4 ++-- .../components/search_index/search_index.tsx | 3 ++- .../utils/indices.test.ts | 15 --------------- .../enterprise_search_content/utils/indices.ts | 11 ----------- 5 files changed, 5 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/enterprise_search/common/types/indices.ts b/x-pack/plugins/enterprise_search/common/types/indices.ts index 78831e1615004..d047ec9ba36d7 100644 --- a/x-pack/plugins/enterprise_search/common/types/indices.ts +++ b/x-pack/plugins/enterprise_search/common/types/indices.ts @@ -36,13 +36,9 @@ export interface ElasticsearchIndex { export interface ConnectorIndex extends ElasticsearchIndex { connector: Connector; } -export interface ConnectorCrawlerIndex extends ElasticsearchIndex { - connector: Connector; - crawler: Crawler; -} export interface CrawlerIndex extends ElasticsearchIndex { - connector?: Connector; crawler: Crawler; + connector?: Connector; } export interface ElasticsearchIndexWithPrivileges extends ElasticsearchIndex { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx index 9c22ecd8572ef..ca9a415c4c958 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx @@ -34,7 +34,7 @@ import { UpdateConnectorSchedulingApiLogic } from '../../../api/connector/update import { SEARCH_INDEX_TAB_PATH } from '../../../routes'; import { IngestionStatus } from '../../../types'; -import { isConnectorIndex, isConnectorCrawlerIndex } from '../../../utils/indices'; +import { isConnectorIndex } from '../../../utils/indices'; import { IndexViewLogic } from '../index_view_logic'; @@ -61,7 +61,7 @@ export const ConnectorSchedulingComponent: React.FC = () => { frequency: schedulingInput?.interval ? cronToFrequency(schedulingInput.interval) : 'HOUR', }); - if (!isConnectorIndex(index) && !isConnectorCrawlerIndex(index)) { + if (!isConnectorIndex(index)) { return <>; } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx index b998fa5d10db2..58494595ad2e4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx @@ -32,6 +32,7 @@ import { IndexCreatedCallout } from './components/index_created_callout/callout' import { IndexCreatedCalloutLogic } from './components/index_created_callout/callout_logic'; import { ConnectorConfiguration } from './connector/connector_configuration'; import { ConnectorSchedulingComponent } from './connector/connector_scheduling'; +import { AutomaticCrawlScheduler } from './crawler/automatic_crawl_scheduler/automatic_crawl_scheduler'; import { CrawlCustomSettingsFlyout } from './crawler/crawl_custom_settings_flyout/crawl_custom_settings_flyout'; import { SearchIndexDomainManagement } from './crawler/domain_management/domain_management'; import { SearchIndexDocuments } from './documents'; @@ -117,7 +118,7 @@ export const SearchIndex: React.FC = () => { }), }, { - content: , + content: , id: SearchIndexTabId.SCHEDULING, name: i18n.translate('xpack.enterpriseSearch.content.searchIndex.schedulingTabLabel', { defaultMessage: 'Scheduling', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.test.ts index b023aff4b9e08..ce6e443a25025 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.test.ts @@ -23,7 +23,6 @@ import { getLastUpdated, indexToViewIndex, isConnectorIndex, - isConnectorCrawlerIndex, isCrawlerIndex, isApiIndex, isConnectorViewIndex, @@ -145,20 +144,6 @@ describe('Indices util functions', () => { expect(isConnectorIndex(apiIndex)).toEqual(false); }); }); - describe('isConnectorCrawlerIndex', () => { - it('should return false for connector indices', () => { - expect(isConnectorCrawlerIndex(connectorIndex)).toEqual(false); - }); - it('should return false for connector-crawler indices', () => { - expect(isConnectorCrawlerIndex(connectorCrawlerIndex)).toEqual(true); - }); - it('should return false for crawler indices', () => { - expect(isConnectorCrawlerIndex(crawlerIndex)).toEqual(false); - }); - it('should return false for API indices', () => { - expect(isConnectorCrawlerIndex(apiIndex)).toEqual(false); - }); - }); describe('isCrawlerIndex', () => { it('should return true for crawler indices', () => { expect(isCrawlerIndex(crawlerIndex)).toEqual(true); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts index 540ad2b2db69e..9a17f7fe84f4d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts @@ -13,7 +13,6 @@ import { ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE } from '../../../../co import { SyncStatus, ConnectorStatus } from '../../../../common/types/connectors'; import { ConnectorIndex, - ConnectorCrawlerIndex, CrawlerIndex, ElasticsearchIndexWithIngestion, } from '../../../../common/types/indices'; @@ -37,16 +36,6 @@ export function isConnectorIndex( ); } -export function isConnectorCrawlerIndex( - index: ElasticsearchIndexWithIngestion | undefined -): index is ConnectorCrawlerIndex { - const crawlerIndex = index as CrawlerIndex; - return ( - !!crawlerIndex?.connector && - crawlerIndex.connector.service_type === ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE - ); -} - export function isCrawlerIndex( index: ElasticsearchIndexWithIngestion | undefined ): index is CrawlerIndex { From 63fd2ce6568277a6154b8c1bbcd4933d96a26586 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 13 Sep 2022 10:43:22 -0600 Subject: [PATCH 26/28] [Maps] fix map app crashing on file upload request timeout (#139760) * [Maps] fix GeoJSON upload fails * remove console.log statement * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * replace slowConnection with uploadSize * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * convert back to switch Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../geo_upload_form/geo_upload_form.tsx | 50 ++++++++++++++++--- .../public/components/geo_upload_wizard.tsx | 9 ++++ .../components/import_complete_view.tsx | 21 +++++--- .../geo/abstract_geo_file_importer.tsx | 8 ++- .../file_upload/public/importer/geo/types.ts | 1 + 5 files changed, 73 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/file_upload/public/components/geo_upload_form/geo_upload_form.tsx b/x-pack/plugins/file_upload/public/components/geo_upload_form/geo_upload_form.tsx index 43dde2580b66d..05b6c6244810f 100644 --- a/x-pack/plugins/file_upload/public/components/geo_upload_form/geo_upload_form.tsx +++ b/x-pack/plugins/file_upload/public/components/geo_upload_form/geo_upload_form.tsx @@ -6,7 +6,15 @@ */ import React, { ChangeEvent, Component } from 'react'; -import { EuiForm, EuiFormRow, EuiSelect } from '@elastic/eui'; +import { + EuiForm, + EuiFormRow, + EuiSpacer, + EuiSelect, + EuiSwitch, + EuiSwitchEvent, + EuiToolTip, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ES_FIELD_TYPES } from '@kbn/data-plugin/public'; import { GeoFilePicker, OnFileSelectParameters } from './geo_file_picker'; @@ -28,12 +36,14 @@ interface Props { geoFieldType: ES_FIELD_TYPES.GEO_POINT | ES_FIELD_TYPES.GEO_SHAPE; indexName: string; indexNameError?: string; + smallChunks: boolean; onFileClear: () => void; onFileSelect: (onFileSelectParameters: OnFileSelectParameters) => void; onGeoFieldTypeSelect: (geoFieldType: ES_FIELD_TYPES.GEO_POINT | ES_FIELD_TYPES.GEO_SHAPE) => void; onIndexNameChange: (name: string, error?: string) => void; onIndexNameValidationStart: () => void; onIndexNameValidationEnd: () => void; + onSmallChunksChange: (smallChunks: boolean) => void; } interface State { @@ -96,6 +106,10 @@ export class GeoUploadForm extends Component { ); }; + _onSmallChunksChange = (event: EuiSwitchEvent) => { + this.props.onSmallChunksChange(event.target.checked); + }; + _renderGeoFieldTypeSelect() { return this.state.hasFile && this.state.isPointsOnly ? ( { {this._renderGeoFieldTypeSelect()} {this.state.hasFile ? ( - + <> + + + + + + + + ) : null} ); diff --git a/x-pack/plugins/file_upload/public/components/geo_upload_wizard.tsx b/x-pack/plugins/file_upload/public/components/geo_upload_wizard.tsx index adbce777a4942..0c7f09c56f36f 100644 --- a/x-pack/plugins/file_upload/public/components/geo_upload_wizard.tsx +++ b/x-pack/plugins/file_upload/public/components/geo_upload_wizard.tsx @@ -40,6 +40,7 @@ interface State { indexNameError?: string; dataViewResp?: object; phase: PHASE; + smallChunks: boolean; } export class GeoUploadWizard extends Component { @@ -52,6 +53,7 @@ export class GeoUploadWizard extends Component importStatus: '', indexName: '', phase: PHASE.CONFIGURE, + smallChunks: false, }; componentDidMount() { @@ -146,6 +148,7 @@ export class GeoUploadWizard extends Component this.setState({ importStatus: getWritingToIndexMsg(0), }); + this._geoFileImporter.setSmallChunks(this.state.smallChunks); const importResults = await this._geoFileImporter.import( initializeImportResp.id, this.state.indexName, @@ -281,6 +284,10 @@ export class GeoUploadWizard extends Component } }; + _onSmallChunksChange = (smallChunks: boolean) => { + this.setState({ smallChunks }); + }; + render() { if (this.state.phase === PHASE.IMPORT) { return ( @@ -311,10 +318,12 @@ export class GeoUploadWizard extends Component indexNameError={this.state.indexNameError} onFileClear={this._onFileClear} onFileSelect={this._onFileSelect} + smallChunks={this.state.smallChunks} onGeoFieldTypeSelect={this._onGeoFieldTypeSelect} onIndexNameChange={this._onIndexNameChange} onIndexNameValidationStart={this.props.disableImportBtn} onIndexNameValidationEnd={this.props.enableImportBtn} + onSmallChunksChange={this._onSmallChunksChange} /> ); } diff --git a/x-pack/plugins/file_upload/public/components/import_complete_view.tsx b/x-pack/plugins/file_upload/public/components/import_complete_view.tsx index 5ec4e6f0ddf37..46f566eb27e2e 100644 --- a/x-pack/plugins/file_upload/public/components/import_complete_view.tsx +++ b/x-pack/plugins/file_upload/public/components/import_complete_view.tsx @@ -128,13 +128,20 @@ export class ImportCompleteView extends Component { } if (!this.props.importResults || !this.props.importResults.success) { - const errorMsg = - this.props.importResults && this.props.importResults.error - ? i18n.translate('xpack.fileUpload.importComplete.uploadFailureMsgErrorBlock', { - defaultMessage: 'Error: {reason}', - values: { reason: this.props.importResults.error.error.reason }, - }) - : ''; + let reason: string | undefined; + if (this.props.importResults?.error?.body?.message) { + // Display http request error message + reason = this.props.importResults.error.body.message; + } else if (this.props.importResults?.error?.error?.reason) { + // Display elasticxsearch request error message + reason = this.props.importResults.error.error.reason; + } + const errorMsg = reason + ? i18n.translate('xpack.fileUpload.importComplete.uploadFailureMsgErrorBlock', { + defaultMessage: 'Error: {reason}', + values: { reason }, + }) + : ''; return ( ; renderEditor(onChange: () => void): ReactNode; setGeoFieldType(geoFieldType: ES_FIELD_TYPES.GEO_POINT | ES_FIELD_TYPES.GEO_SHAPE): void; + setSmallChunks(smallChunks: boolean): void; } From e167ae0f0df13192aa1e6ce295778e42cc1de39f Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 13 Sep 2022 12:16:47 -0500 Subject: [PATCH 27/28] remove invalid document ids --- dev_docs/operations/operations_landing.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/dev_docs/operations/operations_landing.mdx b/dev_docs/operations/operations_landing.mdx index 3a80faa90c501..88e4be9eb93a3 100644 --- a/dev_docs/operations/operations_landing.mdx +++ b/dev_docs/operations/operations_landing.mdx @@ -71,8 +71,6 @@ layout: landing { pageId: "kibDevDocsOpsDevCliRunner" }, { pageId: "kibDevDocsOpsGetRepoFiles" }, { pageId: "kibDevDocsOpsRepoSourceClassifier" }, - { pageId: "kibDevDocsOpsJsonc" }, - { pageId: "kibDevDocsOpsKibanaManifestParser" }, { pageId: "kibDevDocsOpsKibanaManifestSchema" }, { pageId: "kibDevDocsOpsManagedVscodeConfig" }, { pageId: "kibDevDocsOpsManagedVscodeConfigCli" }, From d9b7c3fe5089c7c5916105ba1878b64e4ead54ec Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Tue, 13 Sep 2022 13:25:44 -0400 Subject: [PATCH 28/28] [Synthetics] prevent decryption errors from causing the entire suite of monitors to fail syncing (#140549) * synthetics - prevent decryption errors from causing the entire suite of monitors to fail syncing * adjust decryption error catching logic * Update x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts * Update x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../synthetics_service/synthetics_service.ts | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index 55c0b7f16e14a..908584a7be03e 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -434,17 +434,36 @@ export class SyntheticsService { const start = performance.now(); - const monitors: Array> = await Promise.all( - encryptedMonitors.map((monitor) => - encryptedClient.getDecryptedAsInternalUser( - syntheticsMonitor.name, - monitor.id, - { - namespace: monitor.namespaces?.[0], - } + const monitors: Array> = ( + await Promise.all( + encryptedMonitors.map( + (monitor) => + new Promise((resolve) => { + encryptedClient + .getDecryptedAsInternalUser( + syntheticsMonitor.name, + monitor.id, + { + namespace: monitor.namespaces?.[0], + } + ) + .then((decryptedMonitor) => resolve(decryptedMonitor)) + .catch((e) => { + this.logger.error(e); + sendErrorTelemetryEvents(this.logger, this.server.telemetry, { + reason: 'Failed to decrypt monitor', + message: e?.message, + type: 'runTaskError', + code: e?.code, + status: e.status, + kibanaVersion: this.server.kibanaVersion, + }); + resolve(null); + }); + }) ) ) - ); + ).filter((monitor) => monitor !== null) as Array>; const end = performance.now(); const duration = end - start;