From 8527b6cbe3c7c9fec6090536e96e05399bec29c5 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Tue, 22 Dec 2020 07:48:59 -0500 Subject: [PATCH 01/72] Fixed Result engine display (#86559) --- .../search_experience_content.test.tsx | 38 ++++++++++--------- .../search_experience_content.tsx | 12 ++++-- .../views/result_view.test.tsx | 6 ++- .../search_experience/views/result_view.tsx | 4 +- .../app_search/components/library/library.tsx | 13 ++++--- .../components/result/result.test.tsx | 22 ++++++----- .../app_search/components/result/result.tsx | 8 +++- .../components/result/result_header.test.tsx | 25 +++++++----- .../components/result/result_header.tsx | 7 ++-- .../app_search/components/result/types.ts | 1 - 10 files changed, 82 insertions(+), 54 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx index a46ec560a13e0..8fc1ed5a0a4b6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx @@ -46,30 +46,32 @@ describe('SearchExperienceContent', () => { expect(wrapper.isEmptyRender()).toBe(false); }); - it('passes engineName and schema to the result view', () => { - const props = { - result: { - id: { - raw: '1', - }, - _meta: { - id: '1', - scopedId: '1', - score: 100, - engine: 'my-engine', - }, - foo: { - raw: 'bar', - }, + it('passes result, schema, and isMetaEngine to the result view', () => { + const result = { + id: { + raw: '1', }, - schemaForTypeHighlights: { - title: 'string' as SchemaTypes, + _meta: { + id: '1', + score: 100, + engine: 'my-engine', + }, + foo: { + raw: 'bar', }, }; const wrapper = shallow(); const resultView: any = wrapper.find(Results).prop('resultView'); - expect(resultView(props)).toEqual(); + expect(resultView({ result })).toEqual( + + ); }); it('renders pagination', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx index 55a8377261dd9..b44f3115932a3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx @@ -14,12 +14,12 @@ import { useValues } from 'kea'; import { ResultView } from './views'; import { Pagination } from './pagination'; -import { Props as ResultViewProps } from './views/result_view'; import { useSearchContextState } from './hooks'; import { DocumentCreationButton } from '../document_creation_button'; import { AppLogic } from '../../../app_logic'; import { EngineLogic } from '../../engine'; import { DOCS_PREFIX } from '../../../routes'; +import { Result } from '../../result/types'; export const SearchExperienceContent: React.FC = () => { const { resultSearchTerm, totalResults, wasSearched } = useSearchContextState(); @@ -43,8 +43,14 @@ export const SearchExperienceContent: React.FC = () => { { - return ; + resultView={({ result }: { result: Result }) => { + return ( + + ); }} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx index 91334f312623d..d3a61c12901d3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx @@ -22,7 +22,6 @@ describe('ResultView', () => { }, _meta: { id: '1', - scopedId: '1', score: 100, engine: 'my-engine', }, @@ -33,11 +32,14 @@ describe('ResultView', () => { }; it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(wrapper.find(Result).props()).toEqual({ result, shouldLinkToDetailPage: true, schemaForTypeHighlights: schema, + isMetaEngine: true, }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx index 543c63b334940..2a17dd6128536 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx @@ -13,15 +13,17 @@ import { Result } from '../../../result/result'; export interface Props { result: ResultType; schemaForTypeHighlights?: Schema; + isMetaEngine: boolean; } -export const ResultView: React.FC = ({ result, schemaForTypeHighlights }) => { +export const ResultView: React.FC = ({ result, schemaForTypeHighlights, isMetaEngine }) => { return (
  • ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx index 1b222cfaacf7c..24d2fea973e14 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx @@ -20,13 +20,13 @@ import { Result } from '../result/result'; export const Library: React.FC = () => { const props = { + isMetaEngine: false, result: { id: { raw: '1', }, _meta: { id: '1', - scopedId: '1', score: 100, engine: 'my-engine', }, @@ -98,6 +98,7 @@ export const Library: React.FC = () => { { { { { { }, _meta: { id: 'my-id-is-a-really-long-id-yes-it-is', - scopedId: '2', score: 100, engine: 'my-engine-is-a-really-long-engin-name-yes-it-is', }, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx index 5b598a0b8565e..c4de24e78eae5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx @@ -18,6 +18,7 @@ import { Result } from './result'; describe('Result', () => { const props = { + isMetaEngine: false, result: { id: { raw: '1', @@ -33,7 +34,6 @@ describe('Result', () => { }, _meta: { id: '1', - scopedId: '1', score: 100, engine: 'my-engine', }, @@ -60,14 +60,16 @@ describe('Result', () => { ]); }); - it('passes through showScore and resultMeta to ResultHeader', () => { - const wrapper = shallow(); - expect(wrapper.find(ResultHeader).prop('showScore')).toBe(true); - expect(wrapper.find(ResultHeader).prop('resultMeta')).toEqual({ - id: '1', - scopedId: '1', - score: 100, - engine: 'my-engine', + it('passes showScore, resultMeta, and isMetaEngine to ResultHeader', () => { + const wrapper = shallow(); + expect(wrapper.find(ResultHeader).props()).toEqual({ + isMetaEngine: true, + showScore: true, + resultMeta: { + id: '1', + score: 100, + engine: 'my-engine', + }, }); }); @@ -100,6 +102,7 @@ describe('Result', () => { describe('when there are more than 5 fields', () => { const propsWithMoreFields = { + isMetaEngine: false, result: { id: { raw: '1', @@ -124,7 +127,6 @@ describe('Result', () => { }, _meta: { id: '1', - scopedId: '1', score: 100, engine: 'my-engine', }, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx index 11415f5512380..76b06754d6ce6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx @@ -20,6 +20,7 @@ import { Schema } from '../../../shared/types'; interface Props { result: ResultType; + isMetaEngine: boolean; showScore?: boolean; shouldLinkToDetailPage?: boolean; schemaForTypeHighlights?: Schema; @@ -29,6 +30,7 @@ const RESULT_CUTOFF = 5; export const Result: React.FC = ({ result, + isMetaEngine, showScore = false, shouldLinkToDetailPage = false, schemaForTypeHighlights, @@ -68,7 +70,11 @@ export const Result: React.FC = ({ > {conditionallyLinkedArticle( <> - +
    {resultFields .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx index 95b77a0aed7bb..4ccebb90eb6fe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx @@ -13,57 +13,64 @@ import { ResultHeader } from './result_header'; describe('ResultHeader', () => { const resultMeta = { id: '1', - scopedId: '1', score: 100, engine: 'my-engine', }; it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(wrapper.isEmptyRender()).toBe(false); }); it('always renders an id', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(wrapper.find('[data-test-subj="ResultId"]').prop('value')).toEqual('1'); }); describe('score', () => { it('renders score if showScore is true ', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(wrapper.find('[data-test-subj="ResultScore"]').prop('value')).toEqual(100); }); it('does not render score if showScore is false', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(wrapper.find('[data-test-subj="ResultScore"]').exists()).toBe(false); }); }); describe('engine', () => { - it('renders engine name if the ids dont match, which means it is a meta engine', () => { + it('renders engine name if this is a meta engine', () => { const wrapper = shallow( ); expect(wrapper.find('[data-test-subj="ResultEngine"]').prop('value')).toBe('my-engine'); }); - it('does not render an engine name if the ids match, which means it is not a meta engine', () => { + it('does not render an engine if this is not a meta engine', () => { const wrapper = shallow( ); expect(wrapper.find('[data-test-subj="ResultEngine"]').exists()).toBe(false); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx index 9b83014d041dd..14e0607e1249a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx @@ -13,12 +13,11 @@ import './result_header.scss'; interface Props { showScore: boolean; + isMetaEngine: boolean; resultMeta: ResultMeta; } -export const ResultHeader: React.FC = ({ showScore, resultMeta }) => { - const showEngineLabel: boolean = resultMeta.id !== resultMeta.scopedId; - +export const ResultHeader: React.FC = ({ showScore, resultMeta, isMetaEngine }) => { return (
    {showScore && ( @@ -33,7 +32,7 @@ export const ResultHeader: React.FC = ({ showScore, resultMeta }) => { )}
    - {showEngineLabel && ( + {isMetaEngine && ( Date: Tue, 22 Dec 2020 15:43:01 +0100 Subject: [PATCH 02/72] Fixed the extraction of ServerApiError. (#86732) --- .../trusted_apps/store/middleware.test.ts | 6 ++++-- .../pages/trusted_apps/store/middleware.ts | 20 +++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts index 44f43b90bdd0f..735e63f8e084b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts @@ -179,7 +179,9 @@ describe('middleware', () => { const service = createTrustedAppsServiceMock(); const { store, spyMiddleware } = createStoreSetup(service); - service.getTrustedAppsList.mockRejectedValue(createServerApiError('Internal Server Error')); + service.getTrustedAppsList.mockRejectedValue({ + body: createServerApiError('Internal Server Error'), + }); store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50')); @@ -315,7 +317,7 @@ describe('middleware', () => { const { store, spyMiddleware } = createStoreSetup(service); service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); - service.deleteTrustedApp.mockRejectedValue(notFoundError); + service.deleteTrustedApp.mockRejectedValue({ body: notFoundError }); store.dispatch(createUserChangedUrlAction('/trusted_apps')); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts index 48b2d7113f38e..4508e25d3db33 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts @@ -95,7 +95,7 @@ const refreshListIfNeeded = async ( store.dispatch( createTrustedAppsListResourceStateChangedAction({ type: 'FailedResourceState', - error, + error: error.body, lastLoadedState: getLastLoadedListResourceState(store.getState()), }) ); @@ -103,13 +103,6 @@ const refreshListIfNeeded = async ( } }; -const createTrustedAppDeletionSubmissionResourceStateChanged = ( - newState: Immutable -): Immutable => ({ - type: 'trustedAppDeletionSubmissionResourceStateChanged', - payload: { newState }, -}); - const updateCreationDialogIfNeeded = ( store: ImmutableMiddlewareAPI ) => { @@ -167,7 +160,7 @@ const submitCreationIfNeeded = async ( store.dispatch( createTrustedAppCreationSubmissionResourceStateChanged({ type: 'FailedResourceState', - error, + error: error.body, lastLoadedState: getLastLoadedResourceState(submissionResourceState), }) ); @@ -175,6 +168,13 @@ const submitCreationIfNeeded = async ( } }; +const createTrustedAppDeletionSubmissionResourceStateChanged = ( + newState: Immutable +): Immutable => ({ + type: 'trustedAppDeletionSubmissionResourceStateChanged', + payload: { newState }, +}); + const submitDeletionIfNeeded = async ( store: ImmutableMiddlewareAPI, trustedAppsService: TrustedAppsService @@ -209,7 +209,7 @@ const submitDeletionIfNeeded = async ( store.dispatch( createTrustedAppDeletionSubmissionResourceStateChanged({ type: 'FailedResourceState', - error, + error: error.body, lastLoadedState: getLastLoadedResourceState(submissionResourceState), }) ); From 9bc2fccb2de8e96ce8347cd8fbd0b4514d7949c2 Mon Sep 17 00:00:00 2001 From: Peter Schretlen Date: Tue, 22 Dec 2020 09:54:54 -0500 Subject: [PATCH 03/72] use alerting concepts diagram with sequence, and update docs to explain the sequence (#86699) --- docs/user/alerting/alerting-getting-started.asciidoc | 11 ++++++----- docs/user/alerting/images/alert-concepts-summary.svg | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/user/alerting/alerting-getting-started.asciidoc b/docs/user/alerting/alerting-getting-started.asciidoc index 4eeecad079348..cb2b9b19a0726 100644 --- a/docs/user/alerting/alerting-getting-started.asciidoc +++ b/docs/user/alerting/alerting-getting-started.asciidoc @@ -123,14 +123,15 @@ image::images/alert-concepts-connectors.svg[Connectors provide a central place t [float] === Summary -An _alert_ consists of conditions, _actions_, and a schedule. When conditions are met, _alert instances_ are created that render _actions_ and invoke them. To make action setup and update easier, actions refer to _connectors_ that centralize the information used to connect with {kib} services and third-party integrations. +An _alert_ consists of conditions, _actions_, and a schedule. When conditions are met, _alert instances_ are created that render _actions_ and invoke them. To make action setup and update easier, actions refer to _connectors_ that centralize the information used to connect with {kib} services and third-party integrations. The following example ties these concepts together: image::images/alert-concepts-summary.svg[Alerts, actions, alert instances and connectors work together to convert detection into action] -* *Alert*: a specification of the conditions to be detected, the schedule for detection, and the response when detection occurs. -* *Action*: the response to a detected condition defined in the alert. Typically actions specify a service or third party integration along with alert details that will be sent to it. -* *Alert instance*: state tracked by {kib} for every occurrence of a detected condition. Actions as well as controls like muting and re-notification are controlled at the instance level. -* *Connector*: centralized configurations for services and third party integration that are referenced by actions. +. Anytime an *alert*'s conditions are met, an *alert instance* is created. This example checks for servers with average CPU > 0.9. Three servers meet the condition, so three instances are created. +. Instances create *actions* as long as they are not muted or throttled. When actions are created, the template that was setup in the alert is filled with actual values. In this example three actions are created, and the template string {{server}} is replaced with the server name for each instance. +. {kib} invokes the actions, sending them to a 3rd party *integration* like an email service. +. If the 3rd party integration has connection parameters or credentials, {kib} will fetch these from the *connector* referenced in the action. + [float] [[alerting-concepts-differences]] diff --git a/docs/user/alerting/images/alert-concepts-summary.svg b/docs/user/alerting/images/alert-concepts-summary.svg index 0d63601c0693d..0aed3bf22375f 100644 --- a/docs/user/alerting/images/alert-concepts-summary.svg +++ b/docs/user/alerting/images/alert-concepts-summary.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 23fd0445626951d28252a228ff6369389b7b967f Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Tue, 22 Dec 2020 10:33:27 -0500 Subject: [PATCH 04/72] [Lens] Introduce 4 new calculation functions: counter rate, cumulative sum, differences, and moving average (#84384) * [Lens] UI for reference-based functions * Fix tests * Add a few unit tests for reference editor * Respond to review comments * Update error handling * Update suggestion logic to work with errors and refs * Support ParamEditor in references to fix Last Value: refactoring as needed * Fix error states * Update logic for showing references in dimension editor, add tests * Fix tests Co-authored-by: Joe Reuter Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../workspace_panel/workspace_panel.tsx | 13 +- .../dimension_panel/dimension_editor.tsx | 83 ++- .../dimension_panel/dimension_panel.test.tsx | 113 +++- .../dimension_panel/field_select.tsx | 5 +- .../dimension_panel/operation_support.ts | 2 +- .../dimension_panel/reference_editor.test.tsx | 436 +++++++++++++++ .../dimension_panel/reference_editor.tsx | 306 +++++++++++ .../indexpattern.test.ts | 205 ++----- .../indexpattern_datasource/indexpattern.tsx | 126 ++--- .../indexpattern_suggestions.test.tsx | 507 ++++++++++++++++-- .../indexpattern_suggestions.ts | 96 ++-- .../operations/__mocks__/index.ts | 1 + .../definitions/calculations/counter_rate.tsx | 33 +- .../calculations/cumulative_sum.tsx | 43 +- .../definitions/calculations/derivative.tsx | 38 +- .../calculations/moving_average.tsx | 56 +- .../definitions/calculations/utils.test.ts | 73 +++ .../definitions/calculations/utils.ts | 82 ++- .../operations/definitions/cardinality.tsx | 9 +- .../operations/definitions/count.tsx | 7 +- .../definitions/date_histogram.test.tsx | 28 + .../operations/definitions/date_histogram.tsx | 5 +- .../operations/definitions/helpers.test.ts | 56 ++ .../operations/definitions/helpers.tsx | 9 + .../operations/definitions/index.ts | 11 +- .../definitions/last_value.test.tsx | 9 +- .../operations/definitions/last_value.tsx | 9 +- .../operations/definitions/metrics.tsx | 16 +- .../operations/definitions/ranges/ranges.tsx | 5 +- .../operations/definitions/terms/index.tsx | 38 +- .../definitions/terms/terms.test.tsx | 183 +++++-- .../operations/index.ts | 1 + .../operations/layer_helpers.test.ts | 235 ++++++-- .../operations/layer_helpers.ts | 293 +++++----- .../operations/operations.ts | 11 +- .../operations/time_scale_utils.test.ts | 67 ++- .../operations/time_scale_utils.ts | 16 +- .../public/indexpattern_datasource/utils.ts | 25 +- .../test/functional/apps/lens/smokescreen.ts | 75 +++ .../test/functional/page_objects/lens_page.ts | 39 ++ 40 files changed, 2614 insertions(+), 751 deletions(-) create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.test.ts create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.test.ts diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 8820f26479cf9..99a5869a60872 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -162,7 +162,7 @@ export function WorkspacePanel({ const expression = useMemo( () => { - if (!configurationValidationError || configurationValidationError.length === 0) { + if (!configurationValidationError?.length) { try { return buildExpression({ visualization: activeVisualization, @@ -400,13 +400,17 @@ export const InnerVisualizationWrapper = ({ showExtraErrors = localState.configurationValidationError .slice(1) .map(({ longMessage }) => ( - + {longMessage} )); } else { showExtraErrors = ( - + { setLocalState((prevState: WorkspaceState) => ({ @@ -414,6 +418,7 @@ export const InnerVisualizationWrapper = ({ expandError: !prevState.expandError, })); }} + data-test-subj="configuration-failure-more-errors" > {i18n.translate('xpack.lens.editorFrame.configurationFailureMoreErrors', { defaultMessage: ` +{errors} {errors, plural, one {error} other {errors}}`, @@ -445,7 +450,7 @@ export const InnerVisualizationWrapper = ({ - + {localState.configurationValidationError[0].longMessage} {showExtraErrors} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index c655fc18ab5fa..cc22cbbf57883 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -16,6 +16,7 @@ import { EuiListGroupItemProps, EuiFormLabel, EuiToolTip, + EuiText, } from '@elastic/eui'; import { IndexPatternDimensionEditorProps } from './dimension_panel'; import { OperationSupportMatrix } from './operation_support'; @@ -37,6 +38,7 @@ import { BucketNestingEditor } from './bucket_nesting_editor'; import { IndexPattern, IndexPatternLayer } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { FormatSelector } from './format_selector'; +import { ReferenceEditor } from './reference_editor'; import { TimeScaling } from './time_scaling'; const operationPanels = getOperationDisplay(); @@ -156,7 +158,10 @@ export function DimensionEditor(props: DimensionEditorProps) { (selectedColumn && !hasField(selectedColumn) && definition.input === 'none'), disabledStatus: definition.getDisabledStatus && - definition.getDisabledStatus(state.indexPatterns[state.currentIndexPatternId]), + definition.getDisabledStatus( + state.indexPatterns[state.currentIndexPatternId], + state.layers[layerId] + ), }; }); @@ -180,7 +185,15 @@ export function DimensionEditor(props: DimensionEditorProps) { } let label: EuiListGroupItemProps['label'] = operationPanels[operationType].displayName; - if (disabledStatus) { + if (isActive && disabledStatus) { + label = ( + + + {operationPanels[operationType].displayName} + + + ); + } else if (disabledStatus) { label = ( {operationPanels[operationType].displayName} @@ -202,9 +215,12 @@ export function DimensionEditor(props: DimensionEditorProps) { compatibleWithCurrentField ? '' : ' incompatible' }`, onClick() { - if (operationDefinitionMap[operationType].input === 'none') { + if ( + operationDefinitionMap[operationType].input === 'none' || + operationDefinitionMap[operationType].input === 'fullReference' + ) { + // Clear invalid state because we are reseting to a valid column if (selectedColumn?.operationType === operationType) { - // Clear invalid state because we are reseting to a valid column if (incompleteInfo) { setStateWrapper(resetIncomplete(state.layers[layerId], columnId)); } @@ -291,6 +307,35 @@ export function DimensionEditor(props: DimensionEditorProps) {
    + {!incompleteInfo && + selectedColumn && + 'references' in selectedColumn && + selectedOperationDefinition?.input === 'fullReference' ? ( + <> + {selectedColumn.references.map((referenceId, index) => { + const validation = selectedOperationDefinition.requiredReferences[index]; + + return ( + { + setState(mergeLayer({ state, layerId, newLayer })); + }} + validation={validation} + currentIndexPattern={currentIndexPattern} + existingFields={state.existingFields} + selectionStyle={selectedOperationDefinition.selectionStyle} + dateRange={dateRange} + {...services} + /> + ); + })} + + + ) : null} + {!selectedColumn || selectedOperationDefinition?.input === 'field' || (incompleteOperation && operationDefinitionMap[incompleteOperation].input === 'field') ? ( @@ -325,7 +370,13 @@ export function DimensionEditor(props: DimensionEditorProps) { } incompleteOperation={incompleteOperation} onDeleteColumn={() => { - setStateWrapper(deleteColumn({ layer: state.layers[layerId], columnId })); + setStateWrapper( + deleteColumn({ + layer: state.layers[layerId], + columnId, + indexPattern: currentIndexPattern, + }) + ); }} onChoose={(choice) => { setStateWrapper( @@ -342,15 +393,6 @@ export function DimensionEditor(props: DimensionEditorProps) { ) : null} - {!currentFieldIsInvalid && !incompleteInfo && selectedColumn && ( - - )} - {!currentFieldIsInvalid && !incompleteInfo && selectedColumn && ParamEditor && ( <> )} + + {!currentFieldIsInvalid && !incompleteInfo && selectedColumn && ( + + )}
    @@ -432,11 +483,11 @@ export function DimensionEditor(props: DimensionEditorProps) { } function getErrorMessage( selectedColumn: IndexPatternColumn | undefined, - incompatibleSelectedOperationType: boolean, + incompleteOperation: boolean, input: 'none' | 'field' | 'fullReference' | undefined, fieldInvalid: boolean ) { - if (selectedColumn && incompatibleSelectedOperationType) { + if (selectedColumn && incompleteOperation) { if (input === 'field') { return i18n.translate('xpack.lens.indexPattern.invalidOperationLabel', { defaultMessage: 'To use this function, select a different field.', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 6bfeafd41c6b4..95a6c351e1fc2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -854,6 +854,7 @@ describe('IndexPatternDimensionEditorPanel', () => { dataType: 'date', isBucketed: true, label: '', + customLabel: true, operationType: 'date_histogram', sourceField: 'ts', params: { @@ -872,6 +873,7 @@ describe('IndexPatternDimensionEditorPanel', () => { columnId: 'col2', }; } + it('should not show custom options if time scaling is not available', () => { wrapper = mount( { layers: { first: { ...state.layers.first, + columnOrder: ['col1', 'col2'], columns: { ...state.layers.first.columns, col2: expect.objectContaining({ - sourceField: 'bytes', operationType: 'avg', - // Other parts of this don't matter for this test + sourceField: 'bytes', }), }, - columnOrder: ['col1', 'col2'], + incompleteColumns: {}, }, }, }, @@ -1237,7 +1239,9 @@ describe('IndexPatternDimensionEditorPanel', () => { it('should indicate compatible fields when selecting the operation first', () => { wrapper = mount(); - wrapper.find('button[data-test-subj="lns-indexPatternDimension-avg"]').simulate('click'); + act(() => { + wrapper.find('button[data-test-subj="lns-indexPatternDimension-avg"]').simulate('click'); + }); const options = wrapper .find(EuiComboBox) @@ -1317,10 +1321,14 @@ describe('IndexPatternDimensionEditorPanel', () => { expect(items.map(({ label }: { label: React.ReactNode }) => label)).toEqual([ 'Average', 'Count', + 'Counter rate', + 'Cumulative sum', + 'Differences', 'Last value', 'Maximum', 'Median', 'Minimum', + 'Moving average', 'Sum', 'Unique count', ]); @@ -1536,4 +1544,101 @@ describe('IndexPatternDimensionEditorPanel', () => { }, }); }); + + it('should hide the top level field selector when switching from non-reference to reference', () => { + wrapper = mount(); + + expect(wrapper.find('ReferenceEditor')).toHaveLength(0); + + wrapper + .find('button[data-test-subj="lns-indexPatternDimension-derivative incompatible"]') + .simulate('click'); + + expect(wrapper.find('ReferenceEditor')).toHaveLength(1); + }); + + it('should hide the reference editors when switching from reference to non-reference', () => { + const stateWithReferences: IndexPatternPrivateState = getStateWithColumns({ + col1: { + label: 'Differences of (incomplete)', + dataType: 'number', + isBucketed: false, + operationType: 'derivative', + references: ['col2'], + params: {}, + }, + }); + + wrapper = mount( + + ); + + expect(wrapper.find('ReferenceEditor')).toHaveLength(1); + + wrapper + .find('button[data-test-subj="lns-indexPatternDimension-avg incompatible"]') + .simulate('click'); + + expect(wrapper.find('ReferenceEditor')).toHaveLength(0); + }); + + it('should show a warning when the current dimension is no longer configurable', () => { + const stateWithInvalidCol: IndexPatternPrivateState = getStateWithColumns({ + col1: { + label: 'Invalid derivative', + dataType: 'number', + isBucketed: false, + operationType: 'derivative', + references: ['ref1'], + }, + }); + + wrapper = mount( + + ); + + expect( + wrapper + .find('[data-test-subj="lns-indexPatternDimension-derivative incompatible"]') + .find('EuiText[color="danger"]') + .first() + ).toBeTruthy(); + }); + + it('should remove options to select references when there are no time fields', () => { + const stateWithoutTime: IndexPatternPrivateState = { + ...getStateWithColumns({ + col1: { + label: 'Avg', + dataType: 'number', + isBucketed: false, + operationType: 'avg', + sourceField: 'bytes', + }, + }), + indexPatterns: { + 1: { + id: '1', + title: 'my-fake-index-pattern', + hasRestrictions: false, + fields, + getFieldByName: getFieldByNameFactory([ + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ]), + }, + }, + }; + + wrapper = mount( + + ); + + expect(wrapper.find('[data-test-subj="lns-indexPatternDimension-derivative"]')).toHaveLength(0); + }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx index 406a32f62b2c7..fbdf90e6cc4c7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx @@ -41,6 +41,7 @@ export interface FieldSelectProps extends EuiComboBoxProps<{}> { onDeleteColumn: () => void; existingFields: IndexPatternPrivateState['existingFields']; fieldIsInvalid: boolean; + markAllFieldsCompatible?: boolean; } export function FieldSelect({ @@ -53,6 +54,7 @@ export function FieldSelect({ onDeleteColumn, existingFields, fieldIsInvalid, + markAllFieldsCompatible, ...rest }: FieldSelectProps) { const { operationByField } = operationSupportMatrix; @@ -93,7 +95,7 @@ export function FieldSelect({ : operationByField[field]!.values().next().value, }, exists: containsData(field), - compatible: isCompatibleWithCurrentOperation(field), + compatible: markAllFieldsCompatible || isCompatibleWithCurrentOperation(field), }; }) .sort((a, b) => { @@ -163,6 +165,7 @@ export function FieldSelect({ currentIndexPattern, operationByField, existingFields, + markAllFieldsCompatible, ]); return ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts index 817fdf637f001..9d55a9d5f7522 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts @@ -49,7 +49,7 @@ export const getOperationSupportMatrix = (props: Props): OperationSupportMatrix supportedFieldsByOperation[operation.operationType] = new Set(); } supportedFieldsByOperation[operation.operationType]?.add(operation.field); - } else if (operation.type === 'none') { + } else { supportedOperationsWithoutField.add(operation.operationType); } }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx new file mode 100644 index 0000000000000..0891dd27fcf17 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx @@ -0,0 +1,436 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ReactWrapper, ShallowWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { EuiComboBox } from '@elastic/eui'; +import { mountWithIntl as mount } from '@kbn/test/jest'; +import type { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import type { DataPublicPluginStart } from 'src/plugins/data/public'; +import { OperationMetadata } from '../../types'; +import { createMockedIndexPattern } from '../mocks'; +import { ReferenceEditor, ReferenceEditorProps } from './reference_editor'; +import { insertOrReplaceColumn } from '../operations'; +import { FieldSelect } from './field_select'; + +jest.mock('../operations'); + +describe('reference editor', () => { + let wrapper: ReactWrapper | ShallowWrapper; + let updateLayer: jest.Mock; + + function getDefaultArgs() { + return { + layer: { + indexPatternId: '1', + columns: {}, + columnOrder: [], + }, + columnId: 'ref', + updateLayer, + selectionStyle: 'full' as const, + currentIndexPattern: createMockedIndexPattern(), + existingFields: { + 'my-fake-index-pattern': { + timestamp: true, + bytes: true, + memory: true, + source: true, + }, + }, + dateRange: { fromDate: 'now-1d', toDate: 'now' }, + storage: {} as IStorageWrapper, + uiSettings: {} as IUiSettingsClient, + savedObjectsClient: {} as SavedObjectsClientContract, + http: {} as HttpSetup, + data: {} as DataPublicPluginStart, + }; + } + + beforeEach(() => { + updateLayer = jest.fn().mockImplementation((newLayer) => { + if (wrapper instanceof ReactWrapper) { + wrapper.setProps({ layer: newLayer }); + } + }); + + jest.clearAllMocks(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + } + }); + + it('should indicate that all functions and available fields are compatible in the empty state', () => { + wrapper = mount( + meta.dataType === 'number', + }} + /> + ); + + const functions = wrapper + .find(EuiComboBox) + .filter('[data-test-subj="indexPattern-reference-function"]') + .prop('options'); + + expect(functions).not.toContainEqual( + expect.objectContaining({ 'data-test-subj': expect.stringContaining('Incompatible') }) + ); + + const fields = wrapper + .find(EuiComboBox) + .filter('[data-test-subj="indexPattern-dimension-field"]') + .prop('options'); + + expect(fields![0].options).not.toContainEqual( + expect.objectContaining({ 'data-test-subj': expect.stringContaining('Incompatible') }) + ); + expect(fields![1].options).not.toContainEqual( + expect.objectContaining({ 'data-test-subj': expect.stringContaining('Incompatible') }) + ); + }); + + it('should indicate functions and fields that are incompatible with the current', () => { + wrapper = mount( + meta.isBucketed, + }} + /> + ); + + const functions = wrapper + .find(EuiComboBox) + .filter('[data-test-subj="indexPattern-reference-function"]') + .prop('options'); + expect(functions.find(({ label }) => label === 'Date histogram')!['data-test-subj']).toContain( + 'incompatible' + ); + + const fields = wrapper + .find(EuiComboBox) + .filter('[data-test-subj="indexPattern-dimension-field"]') + .prop('options'); + expect( + fields![0].options!.find(({ label }) => label === 'timestampLabel')!['data-test-subj'] + ).toContain('Incompatible'); + }); + + it('should not update when selecting the same operation', () => { + wrapper = mount( + meta.dataType === 'number', + }} + /> + ); + + const comboBox = wrapper + .find(EuiComboBox) + .filter('[data-test-subj="indexPattern-reference-function"]'); + const option = comboBox.prop('options')!.find(({ label }) => label === 'Average')!; + + act(() => { + comboBox.prop('onChange')!([option]); + }); + expect(insertOrReplaceColumn).not.toHaveBeenCalled(); + }); + + it('should keep the field when replacing an existing reference with a compatible function', () => { + wrapper = mount( + meta.dataType === 'number', + }} + /> + ); + + const comboBox = wrapper + .find(EuiComboBox) + .filter('[data-test-subj="indexPattern-reference-function"]'); + const option = comboBox.prop('options')!.find(({ label }) => label === 'Maximum')!; + + act(() => { + comboBox.prop('onChange')!([option]); + }); + + expect(insertOrReplaceColumn).toHaveBeenCalledWith( + expect.objectContaining({ + op: 'max', + field: expect.objectContaining({ name: 'bytes' }), + }) + ); + }); + + it('should transition to another function with incompatible field', () => { + wrapper = mount( + true, + }} + /> + ); + + const comboBox = wrapper + .find(EuiComboBox) + .filter('[data-test-subj="indexPattern-reference-function"]'); + const option = comboBox.prop('options')!.find(({ label }) => label === 'Date histogram')!; + + act(() => { + comboBox.prop('onChange')!([option]); + }); + + expect(insertOrReplaceColumn).toHaveBeenCalledWith( + expect.objectContaining({ + op: 'date_histogram', + field: undefined, + }) + ); + }); + + it('should hide the function selector when using a field-only selection style', () => { + wrapper = mount( + true, + }} + /> + ); + + const comboBox = wrapper + .find(EuiComboBox) + .filter('[data-test-subj="indexPattern-reference-function"]'); + expect(comboBox).toHaveLength(0); + }); + + it('should pass the incomplete operation info to FieldSelect', () => { + wrapper = mount( + true, + }} + /> + ); + + const fieldSelect = wrapper.find(FieldSelect); + expect(fieldSelect.prop('fieldIsInvalid')).toEqual(true); + expect(fieldSelect.prop('selectedField')).toEqual('bytes'); + expect(fieldSelect.prop('selectedOperationType')).toEqual('avg'); + expect(fieldSelect.prop('incompleteOperation')).toEqual('max'); + expect(fieldSelect.prop('markAllFieldsCompatible')).toEqual(false); + }); + + it('should pass the incomplete field info to FieldSelect', () => { + wrapper = mount( + true, + }} + /> + ); + + const fieldSelect = wrapper.find(FieldSelect); + expect(fieldSelect.prop('fieldIsInvalid')).toEqual(false); + expect(fieldSelect.prop('selectedField')).toEqual('timestamp'); + expect(fieldSelect.prop('selectedOperationType')).toEqual('avg'); + expect(fieldSelect.prop('incompleteOperation')).toBeUndefined(); + }); + + it('should show the FieldSelect as invalid in the empty state for field-only forms', () => { + wrapper = mount( + true, + }} + /> + ); + + const fieldSelect = wrapper.find(FieldSelect); + expect(fieldSelect.prop('fieldIsInvalid')).toEqual(true); + expect(fieldSelect.prop('selectedField')).toBeUndefined(); + expect(fieldSelect.prop('selectedOperationType')).toBeUndefined(); + expect(fieldSelect.prop('incompleteOperation')).toBeUndefined(); + expect(fieldSelect.prop('markAllFieldsCompatible')).toEqual(true); + }); + + it('should show the ParamEditor for functions that offer one', () => { + wrapper = mount( + true, + }} + /> + ); + + expect(wrapper.find('[data-test-subj="lns-indexPattern-lastValue-sortField"]').exists()).toBe( + true + ); + }); + + it('should hide the ParamEditor for incomplete functions', () => { + wrapper = mount( + true, + }} + /> + ); + + expect(wrapper.find('[data-test-subj="lns-indexPattern-lastValue-sortField"]').exists()).toBe( + false + ); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx new file mode 100644 index 0000000000000..d73530ec8a920 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx @@ -0,0 +1,306 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import './dimension_editor.scss'; +import _ from 'lodash'; +import React, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFormRow, EuiSpacer, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import type { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import type { DataPublicPluginStart } from 'src/plugins/data/public'; +import type { DateRange } from '../../../common'; +import type { OperationSupportMatrix } from './operation_support'; +import type { OperationType } from '../indexpattern'; +import { + operationDefinitionMap, + getOperationDisplay, + insertOrReplaceColumn, + deleteColumn, + isOperationAllowedAsReference, + FieldBasedIndexPatternColumn, + RequiredReference, +} from '../operations'; +import { FieldSelect } from './field_select'; +import { hasField } from '../utils'; +import type { IndexPattern, IndexPatternLayer, IndexPatternPrivateState } from '../types'; +import { trackUiEvent } from '../../lens_ui_telemetry'; + +const operationPanels = getOperationDisplay(); + +export interface ReferenceEditorProps { + layer: IndexPatternLayer; + selectionStyle: 'full' | 'field'; + validation: RequiredReference; + columnId: string; + updateLayer: (newLayer: IndexPatternLayer) => void; + currentIndexPattern: IndexPattern; + existingFields: IndexPatternPrivateState['existingFields']; + dateRange: DateRange; + + // Services + uiSettings: IUiSettingsClient; + storage: IStorageWrapper; + savedObjectsClient: SavedObjectsClientContract; + http: HttpSetup; + data: DataPublicPluginStart; +} + +export function ReferenceEditor(props: ReferenceEditorProps) { + const { + layer, + columnId, + updateLayer, + currentIndexPattern, + existingFields, + validation, + selectionStyle, + dateRange, + ...services + } = props; + + const column = layer.columns[columnId]; + const selectedOperationDefinition = column && operationDefinitionMap[column.operationType]; + + const ParamEditor = selectedOperationDefinition?.paramEditor; + + const incompleteInfo = layer.incompleteColumns ? layer.incompleteColumns[columnId] : undefined; + const incompleteOperation = incompleteInfo?.operationType; + const incompleteField = incompleteInfo?.sourceField ?? null; + + // Basically the operation support matrix, but different validation + const operationSupportMatrix: OperationSupportMatrix & { + operationTypes: Set; + } = useMemo(() => { + const operationTypes: Set = new Set(); + const operationWithoutField: Set = new Set(); + const operationByField: Partial>> = {}; + const fieldByOperation: Partial>> = {}; + Object.values(operationDefinitionMap) + .sort((op1, op2) => { + return op1.displayName.localeCompare(op2.displayName); + }) + .forEach((op) => { + if (op.input === 'field') { + const allFields = currentIndexPattern.fields.filter((field) => + isOperationAllowedAsReference({ + operationType: op.type, + validation, + field, + indexPattern: currentIndexPattern, + }) + ); + if (allFields.length) { + operationTypes.add(op.type); + fieldByOperation[op.type] = new Set(allFields.map(({ name }) => name)); + allFields.forEach((field) => { + if (!operationByField[field.name]) { + operationByField[field.name] = new Set(); + } + operationByField[field.name]?.add(op.type); + }); + } + } else if ( + isOperationAllowedAsReference({ + operationType: op.type, + validation, + indexPattern: currentIndexPattern, + }) + ) { + operationTypes.add(op.type); + operationWithoutField.add(op.type); + } + }); + return { + operationTypes, + operationWithoutField, + operationByField, + fieldByOperation, + }; + }, [currentIndexPattern, validation]); + + const functionOptions: Array> = Array.from( + operationSupportMatrix.operationTypes + ).map((operationType) => { + const def = operationDefinitionMap[operationType]; + const label = operationPanels[operationType].displayName; + const isCompatible = + !column || + (column && + hasField(column) && + def.input === 'field' && + operationSupportMatrix.fieldByOperation[operationType]?.has(column.sourceField)) || + (column && !hasField(column) && def.input !== 'field'); + + return { + label, + value: operationType, + className: 'lnsIndexPatternDimensionEditor__operation', + 'data-test-subj': `lns-indexPatternDimension-${operationType}${ + isCompatible ? '' : ' incompatible' + }`, + }; + }); + + function onChooseFunction(operationType: OperationType) { + if (column?.operationType === operationType) { + return; + } + const possibleFieldNames = operationSupportMatrix.fieldByOperation[operationType]; + if (column && 'sourceField' in column && possibleFieldNames?.has(column.sourceField)) { + // Reuse the current field if possible + updateLayer( + insertOrReplaceColumn({ + layer, + columnId, + op: operationType, + indexPattern: currentIndexPattern, + field: currentIndexPattern.getFieldByName(column.sourceField), + }) + ); + } else { + // If reusing the field is impossible, we generally can't choose for the user. + // The one exception is if the field is the only possible field, like Count of Records. + const possibleField = + possibleFieldNames?.size === 1 + ? currentIndexPattern.getFieldByName(possibleFieldNames.values().next().value) + : undefined; + + updateLayer( + insertOrReplaceColumn({ + layer, + columnId, + op: operationType, + indexPattern: currentIndexPattern, + field: possibleField, + }) + ); + } + trackUiEvent(`indexpattern_dimension_operation_${operationType}`); + return; + } + + const selectedOption = incompleteInfo?.operationType + ? [functionOptions.find(({ value }) => value === incompleteInfo.operationType)!] + : column + ? [functionOptions.find(({ value }) => value === column.operationType)!] + : []; + + // If the operationType is incomplete, the user needs to select a field- so + // the function is marked as valid. + const showOperationInvalid = !column && !Boolean(incompleteInfo?.operationType); + // The field is invalid if the operation has been updated without a field, + // or if we are in a field-only mode but empty state + const showFieldInvalid = + Boolean(incompleteInfo?.operationType) || (selectionStyle === 'field' && !column); + + return ( +
    +
    + {selectionStyle !== 'field' ? ( + <> + + { + if (choices.length === 0) { + updateLayer( + deleteColumn({ layer, columnId, indexPattern: currentIndexPattern }) + ); + return; + } + + trackUiEvent('indexpattern_dimension_field_changed'); + + onChooseFunction(choices[0].value!); + }} + /> + + + + ) : null} + + {!column || selectedOperationDefinition.input === 'field' ? ( + + { + updateLayer(deleteColumn({ layer, columnId, indexPattern: currentIndexPattern })); + }} + onChoose={(choice) => { + updateLayer( + insertOrReplaceColumn({ + layer, + columnId, + indexPattern: currentIndexPattern, + op: choice.operationType, + field: currentIndexPattern.getFieldByName(choice.field), + }) + ); + }} + /> + + ) : null} + + {column && !incompleteInfo && ParamEditor && ( + <> + + + )} +
    +
    + ); +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 2e55abf4a429a..7e67d863346c7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -858,165 +858,49 @@ describe('IndexPattern Data Source', () => { it('should return null for non-existant columns', () => { expect(publicAPI.getOperationForColumnId('col2')).toBe(null); }); - }); - }); - describe('#getErrorMessages', () => { - it('should detect a missing reference in a layer', () => { - const state = { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, - layers: { - first: { - indexPatternId: '1', - columnOrder: ['col1'], - columns: { - col1: { - dataType: 'number', - isBucketed: false, - label: 'Foo', - operationType: 'count', // <= invalid - sourceField: 'bytes', - }, - }, - }, - }, - currentIndexPatternId: '1', - }; - const messages = indexPatternDatasource.getErrorMessages(state as IndexPatternPrivateState); - expect(messages).toHaveLength(1); - expect(messages![0]).toEqual({ - shortMessage: 'Invalid reference.', - longMessage: '"Foo" has an invalid reference.', - }); - }); - - it('should detect and batch missing references in a layer', () => { - const state = { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, - layers: { - first: { - indexPatternId: '1', - columnOrder: ['col1', 'col2'], - columns: { - col1: { - dataType: 'number', - isBucketed: false, - label: 'Foo', - operationType: 'count', // <= invalid - sourceField: 'bytes', - }, - col2: { - dataType: 'number', - isBucketed: false, - label: 'Foo2', - operationType: 'count', // <= invalid - sourceField: 'memory', - }, - }, - }, - }, - currentIndexPatternId: '1', - }; - const messages = indexPatternDatasource.getErrorMessages(state as IndexPatternPrivateState); - expect(messages).toHaveLength(1); - expect(messages![0]).toEqual({ - shortMessage: 'Invalid references.', - longMessage: '"Foo", "Foo2" have invalid reference.', - }); - }); + it('should return null for referenced columns', () => { + publicAPI = indexPatternDatasource.getPublicAPI({ + state: { + ...enrichBaseState(baseState), + layers: { + first: { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: { + label: 'Sum', + dataType: 'number', + isBucketed: false, - it('should detect and batch missing references in multiple layers', () => { - const state = { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, - layers: { - first: { - indexPatternId: '1', - columnOrder: ['col1', 'col2'], - columns: { - col1: { - dataType: 'number', - isBucketed: false, - label: 'Foo', - operationType: 'count', // <= invalid - sourceField: 'bytes', - }, - col2: { - dataType: 'number', - isBucketed: false, - label: 'Foo2', - operationType: 'count', // <= invalid - sourceField: 'memory', - }, - }, - }, - second: { - indexPatternId: '1', - columnOrder: ['col1'], - columns: { - col1: { - dataType: 'string', - isBucketed: false, - label: 'Foo', - operationType: 'count', // <= invalid - sourceField: 'source', - }, - }, - }, - }, - currentIndexPatternId: '1', - }; - const messages = indexPatternDatasource.getErrorMessages(state as IndexPatternPrivateState); - expect(messages).toHaveLength(2); - expect(messages).toEqual([ - { - shortMessage: 'Invalid references on Layer 1.', - longMessage: 'Layer 1 has invalid references in "Foo", "Foo2".', - }, - { - shortMessage: 'Invalid reference on Layer 2.', - longMessage: 'Layer 2 has an invalid reference in "Foo".', - }, - ]); - }); + operationType: 'sum', + sourceField: 'test', + params: {}, + } as IndexPatternColumn, + col2: { + label: 'Cumulative sum', + dataType: 'number', + isBucketed: false, - it('should return no errors if all references are satified', () => { - const state = { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, - layers: { - first: { - indexPatternId: '1', - columnOrder: ['col1'], - columns: { - col1: { - dataType: 'number', - isBucketed: false, - label: 'Foo', - operationType: 'avg', - sourceField: 'bytes', + operationType: 'cumulative_sum', + references: ['col1'], + params: {}, + } as IndexPatternColumn, + }, }, }, }, - }, - currentIndexPatternId: '1', - }; - expect( - indexPatternDatasource.getErrorMessages(state as IndexPatternPrivateState) - ).toBeUndefined(); + layerId: 'first', + }); + expect(publicAPI.getOperationForColumnId('col1')).toEqual(null); + }); }); + }); - it('should return no errors with layers with no columns', () => { + describe('#getErrorMessages', () => { + it('should use the results of getErrorMessages directly when single layer', () => { + (getErrorMessages as jest.Mock).mockClear(); + (getErrorMessages as jest.Mock).mockReturnValueOnce(['error 1', 'error 2']); const state: IndexPatternPrivateState = { indexPatternRefs: [], existingFields: {}, @@ -1031,10 +915,14 @@ describe('IndexPattern Data Source', () => { }, currentIndexPatternId: '1', }; - expect(indexPatternDatasource.getErrorMessages(state)).toBeUndefined(); + expect(indexPatternDatasource.getErrorMessages(state)).toEqual([ + { longMessage: 'error 1', shortMessage: '' }, + { longMessage: 'error 2', shortMessage: '' }, + ]); + expect(getErrorMessages).toHaveBeenCalledTimes(1); }); - it('should bubble up invalid configuration from operations', () => { + it('should prepend each error with its layer number on multi-layer chart', () => { (getErrorMessages as jest.Mock).mockClear(); (getErrorMessages as jest.Mock).mockReturnValueOnce(['error 1', 'error 2']); const state: IndexPatternPrivateState = { @@ -1048,14 +936,19 @@ describe('IndexPattern Data Source', () => { columnOrder: [], columns: {}, }, + second: { + indexPatternId: '1', + columnOrder: [], + columns: {}, + }, }, currentIndexPatternId: '1', }; expect(indexPatternDatasource.getErrorMessages(state)).toEqual([ - { shortMessage: 'error 1', longMessage: '' }, - { shortMessage: 'error 2', longMessage: '' }, + { longMessage: 'Layer 1 error: error 1', shortMessage: '' }, + { longMessage: 'Layer 1 error: error 2', shortMessage: '' }, ]); - expect(getErrorMessages).toHaveBeenCalledTimes(1); + expect(getErrorMessages).toHaveBeenCalledTimes(2); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 2937b1cf05760..6c6bd2e1bb439 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -39,12 +39,7 @@ import { getDatasourceSuggestionsForVisualizeField, } from './indexpattern_suggestions'; -import { - getInvalidColumnsForLayer, - getInvalidLayers, - isDraggedField, - normalizeOperationDataType, -} from './utils'; +import { isDraggedField, normalizeOperationDataType } from './utils'; import { LayerPanel } from './layerpanel'; import { IndexPatternColumn, getErrorMessages, IncompleteColumn } from './operations'; import { IndexPatternField, IndexPatternPrivateState, IndexPatternPersistedState } from './types'; @@ -55,7 +50,6 @@ import { mergeLayer } from './state_helpers'; import { Datasource, StateSetter } from '../index'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; import { deleteColumn, isReferenced } from './operations'; -import { FieldBasedIndexPatternColumn } from './operations/definitions/column_types'; import { Dragging } from '../drag_drop/providers'; export { OperationType, IndexPatternColumn, deleteColumn } from './operations'; @@ -162,10 +156,11 @@ export function getIndexPatternDatasource({ }, removeColumn({ prevState, layerId, columnId }) { + const indexPattern = prevState.indexPatterns[prevState.layers[layerId]?.indexPatternId]; return mergeLayer({ state: prevState, layerId, - newLayer: deleteColumn({ layer: prevState.layers[layerId], columnId }), + newLayer: deleteColumn({ layer: prevState.layers[layerId], columnId, indexPattern }), }); }, @@ -351,7 +346,9 @@ export function getIndexPatternDatasource({ const layer = state.layers[layerId]; if (layer && layer.columns[columnId]) { - return columnToOperation(layer.columns[columnId], columnLabelMap[columnId]); + if (!isReferenced(layer, columnId)) { + return columnToOperation(layer.columns[columnId], columnLabelMap[columnId]); + } } return null; }, @@ -369,91 +366,46 @@ export function getIndexPatternDatasource({ if (!state) { return; } - const invalidLayers = getInvalidLayers(state); - const layerErrors = Object.values(state.layers).flatMap((layer) => + const layerErrors = Object.values(state.layers).map((layer) => (getErrorMessages(layer) ?? []).map((message) => ({ - shortMessage: message, - longMessage: '', + shortMessage: '', // Not displayed currently + longMessage: message, })) ); - if (invalidLayers.length === 0) { - return layerErrors.length ? layerErrors : undefined; + // Single layer case, no need to explain more + if (layerErrors.length <= 1) { + return layerErrors[0]?.length ? layerErrors[0] : undefined; } - const realIndex = Object.values(state.layers) - .map((layer, i) => { - const filteredIndex = invalidLayers.indexOf(layer); - if (filteredIndex > -1) { - return [filteredIndex, i + 1]; - } - }) - .filter(Boolean) as Array<[number, number]>; - const invalidColumnsForLayer: string[][] = getInvalidColumnsForLayer( - invalidLayers, - state.indexPatterns - ); - const originalLayersList = Object.keys(state.layers); - - if (layerErrors.length || realIndex.length) { - return [ - ...layerErrors, - ...realIndex.map(([filteredIndex, layerIndex]) => { - const columnLabelsWithBrokenReferences: string[] = invalidColumnsForLayer[ - filteredIndex - ].map((columnId) => { - const column = invalidLayers[filteredIndex].columns[ - columnId - ] as FieldBasedIndexPatternColumn; - return column.label; - }); - - if (originalLayersList.length === 1) { - return { - shortMessage: i18n.translate( - 'xpack.lens.indexPattern.dataReferenceFailureShortSingleLayer', - { - defaultMessage: - 'Invalid {columns, plural, one {reference} other {references}}.', - values: { - columns: columnLabelsWithBrokenReferences.length, - }, - } - ), - longMessage: i18n.translate( - 'xpack.lens.indexPattern.dataReferenceFailureLongSingleLayer', - { - defaultMessage: `"{columns}" {columnsLength, plural, one {has an} other {have}} invalid reference.`, - values: { - columns: columnLabelsWithBrokenReferences.join('", "'), - columnsLength: columnLabelsWithBrokenReferences.length, - }, - } - ), - }; - } - return { - shortMessage: i18n.translate('xpack.lens.indexPattern.dataReferenceFailureShort', { - defaultMessage: - 'Invalid {columnsLength, plural, one {reference} other {references}} on Layer {layer}.', - values: { - layer: layerIndex, - columnsLength: columnLabelsWithBrokenReferences.length, - }, - }), - longMessage: i18n.translate('xpack.lens.indexPattern.dataReferenceFailureLong', { - defaultMessage: `Layer {layer} has {columnsLength, plural, one {an invalid} other {invalid}} {columnsLength, plural, one {reference} other {references}} in "{columns}".`, - values: { - layer: layerIndex, - columns: columnLabelsWithBrokenReferences.join('", "'), - columnsLength: columnLabelsWithBrokenReferences.length, - }, - }), - }; - }), - ]; - } + // For multiple layers we will prepend each error with the layer number + const messages = layerErrors.flatMap((errors, index) => { + return errors.map((error) => { + const { shortMessage, longMessage } = error; + return { + shortMessage: shortMessage + ? i18n.translate('xpack.lens.indexPattern.layerErrorWrapper', { + defaultMessage: 'Layer {position} error: {wrappedMessage}', + values: { + position: index + 1, + wrappedMessage: shortMessage, + }, + }) + : '', + longMessage: longMessage + ? i18n.translate('xpack.lens.indexPattern.layerErrorWrapper', { + defaultMessage: 'Layer {position} error: {wrappedMessage}', + values: { + position: index + 1, + wrappedMessage: longMessage, + }, + }) + : '', + }; + }); + }); + return messages.length ? messages : undefined; }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index 9fbad553d441a..de768e92efb3d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -6,11 +6,12 @@ import { DatasourceSuggestion } from '../types'; import { generateId } from '../id_generator'; -import { IndexPatternPrivateState } from './types'; +import type { IndexPatternPrivateState } from './types'; import { getDatasourceSuggestionsForField, getDatasourceSuggestionsFromCurrentState, getDatasourceSuggestionsForVisualizeField, + IndexPatternSuggestion, } from './indexpattern_suggestions'; import { documentField } from './document_field'; import { getFieldByNameFactory } from './pure_helpers'; @@ -153,6 +154,7 @@ function testInitialState(): IndexPatternPrivateState { columns: { col1: { label: 'My Op', + customLabel: true, dataType: 'string', isBucketed: true, @@ -172,6 +174,19 @@ function testInitialState(): IndexPatternPrivateState { }; } +// Simplifies the debug output for failed test +function getSuggestionSubset( + suggestions: IndexPatternSuggestion[] +): Array> { + return suggestions.map((s) => { + const newSuggestion = { ...s } as Omit & { + state?: IndexPatternPrivateState; + }; + delete newSuggestion.state; + return newSuggestion; + }); +} + describe('IndexPattern Data Source suggestions', () => { beforeEach(async () => { let count = 0; @@ -698,6 +713,7 @@ describe('IndexPattern Data Source suggestions', () => { isBucketed: true, sourceField: 'source', label: 'values of source', + customLabel: true, operationType: 'terms', params: { orderBy: { type: 'column', columnId: 'colb' }, @@ -710,6 +726,7 @@ describe('IndexPattern Data Source suggestions', () => { isBucketed: false, sourceField: 'bytes', label: 'Avg of bytes', + customLabel: true, operationType: 'avg', }, }, @@ -733,7 +750,7 @@ describe('IndexPattern Data Source suggestions', () => { dataType: 'date', isBucketed: true, sourceField: 'timestamp', - label: 'date histogram of timestamp', + label: 'timestamp', operationType: 'date_histogram', params: { interval: 'w', @@ -744,6 +761,7 @@ describe('IndexPattern Data Source suggestions', () => { isBucketed: false, sourceField: 'bytes', label: 'Avg of bytes', + customLabel: true, operationType: 'avg', }, }, @@ -782,6 +800,7 @@ describe('IndexPattern Data Source suggestions', () => { }); it('puts a date histogram column after the last bucket column on date field', () => { + (generateId as jest.Mock).mockReturnValue('newid'); const initialState = stateWithNonEmptyTables(); const suggestions = getDatasourceSuggestionsForField(initialState, '1', { name: 'timestamp', @@ -790,17 +809,16 @@ describe('IndexPattern Data Source suggestions', () => { aggregatable: true, searchable: true, }); - expect(suggestions).toContainEqual( expect.objectContaining({ state: expect.objectContaining({ layers: { previousLayer: initialState.layers.previousLayer, currentLayer: expect.objectContaining({ - columnOrder: ['cola', 'id1', 'colb'], + columnOrder: ['cola', 'newid', 'colb'], columns: { ...initialState.layers.currentLayer.columns, - id1: expect.objectContaining({ + newid: expect.objectContaining({ operationType: 'date_histogram', sourceField: 'timestamp', }), @@ -817,7 +835,7 @@ describe('IndexPattern Data Source suggestions', () => { columnId: 'cola', }), expect.objectContaining({ - columnId: 'id1', + columnId: 'newid', }), expect.objectContaining({ columnId: 'colb', @@ -845,6 +863,7 @@ describe('IndexPattern Data Source suggestions', () => { }); it('appends a terms column with default size on string field', () => { + (generateId as jest.Mock).mockReturnValue('newid'); const initialState = stateWithNonEmptyTables(); const suggestions = getDatasourceSuggestionsForField(initialState, '1', { name: 'dest', @@ -853,17 +872,16 @@ describe('IndexPattern Data Source suggestions', () => { aggregatable: true, searchable: true, }); - expect(suggestions).toContainEqual( expect.objectContaining({ state: expect.objectContaining({ layers: { previousLayer: initialState.layers.previousLayer, currentLayer: expect.objectContaining({ - columnOrder: ['cola', 'id1', 'colb'], + columnOrder: ['cola', 'newid', 'colb'], columns: { ...initialState.layers.currentLayer.columns, - id1: expect.objectContaining({ + newid: expect.objectContaining({ operationType: 'terms', sourceField: 'dest', params: expect.objectContaining({ size: 3 }), @@ -877,6 +895,7 @@ describe('IndexPattern Data Source suggestions', () => { }); it('suggests both replacing and adding metric if only one other metric is set', () => { + (generateId as jest.Mock).mockReturnValue('newid'); const initialState = stateWithNonEmptyTables(); const suggestions = getDatasourceSuggestionsForField(initialState, '1', { name: 'memory', @@ -885,7 +904,6 @@ describe('IndexPattern Data Source suggestions', () => { aggregatable: true, searchable: true, }); - expect(suggestions).toContainEqual( expect.objectContaining({ state: expect.objectContaining({ @@ -910,11 +928,11 @@ describe('IndexPattern Data Source suggestions', () => { state: expect.objectContaining({ layers: expect.objectContaining({ currentLayer: expect.objectContaining({ - columnOrder: ['cola', 'colb', 'id1'], + columnOrder: ['cola', 'colb', 'newid'], columns: { cola: initialState.layers.currentLayer.columns.cola, colb: initialState.layers.currentLayer.columns.colb, - id1: expect.objectContaining({ + newid: expect.objectContaining({ operationType: 'avg', sourceField: 'memory', }), @@ -927,6 +945,7 @@ describe('IndexPattern Data Source suggestions', () => { }); it('adds a metric column on a number field if no other metrics set', () => { + (generateId as jest.Mock).mockReturnValue('newid'); const initialState = stateWithNonEmptyTables(); const modifiedState: IndexPatternPrivateState = { ...initialState, @@ -955,10 +974,10 @@ describe('IndexPattern Data Source suggestions', () => { layers: { previousLayer: modifiedState.layers.previousLayer, currentLayer: expect.objectContaining({ - columnOrder: ['cola', 'id1'], + columnOrder: ['cola', 'newid'], columns: { ...modifiedState.layers.currentLayer.columns, - id1: expect.objectContaining({ + newid: expect.objectContaining({ operationType: 'avg', sourceField: 'memory', }), @@ -1008,6 +1027,137 @@ describe('IndexPattern Data Source suggestions', () => { const suggestions = getDatasourceSuggestionsForField(modifiedState, '1', documentField); expect(suggestions).not.toContain(expect.objectContaining({ changeType: 'extended' })); }); + + it('hides any referenced metrics when adding new metrics', () => { + (generateId as jest.Mock).mockReturnValue('newid'); + const initialState = stateWithNonEmptyTables(); + const modifiedState: IndexPatternPrivateState = { + ...initialState, + layers: { + currentLayer: { + indexPatternId: '1', + columnOrder: ['date', 'metric', 'ref'], + columns: { + date: { + label: '', + customLabel: true, + dataType: 'date', + isBucketed: true, + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { interval: 'auto' }, + }, + metric: { + label: '', + customLabel: true, + dataType: 'number', + isBucketed: false, + operationType: 'avg', + sourceField: 'bytes', + }, + ref: { + label: '', + customLabel: true, + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + references: ['metric'], + }, + }, + }, + }, + }; + const suggestions = getSuggestionSubset( + getDatasourceSuggestionsForField(modifiedState, '1', documentField) + ); + expect(suggestions).toContainEqual( + expect.objectContaining({ + table: expect.objectContaining({ + isMultiRow: true, + changeType: 'extended', + label: undefined, + layerId: 'currentLayer', + columns: [ + { + columnId: 'date', + operation: expect.objectContaining({ dataType: 'date', isBucketed: true }), + }, + { + columnId: 'newid', + operation: expect.objectContaining({ dataType: 'number', isBucketed: false }), + }, + { + columnId: 'ref', + operation: expect.objectContaining({ dataType: 'number', isBucketed: false }), + }, + ], + }), + keptLayerIds: ['currentLayer'], + }) + ); + }); + + it('makes a suggestion to extending from an invalid state with a new metric', () => { + (generateId as jest.Mock).mockReturnValue('newid'); + const initialState = stateWithNonEmptyTables(); + const modifiedState: IndexPatternPrivateState = { + ...initialState, + layers: { + currentLayer: { + indexPatternId: '1', + columnOrder: ['metric', 'ref'], + columns: { + metric: { + label: '', + customLabel: true, + dataType: 'number', + isBucketed: false, + operationType: 'avg', + sourceField: 'bytes', + }, + ref: { + label: '', + customLabel: true, + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + references: ['metric'], + }, + }, + }, + }, + }; + const suggestions = getSuggestionSubset( + getDatasourceSuggestionsForField(modifiedState, '1', documentField) + ); + expect(suggestions).toContainEqual( + expect.objectContaining({ + table: expect.objectContaining({ + changeType: 'extended', + columns: [ + { + columnId: 'newid', + operation: { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + scale: 'ratio', + }, + }, + { + columnId: 'ref', + operation: { + dataType: 'number', + isBucketed: false, + label: '', + scale: undefined, + }, + }, + ], + }), + }) + ); + }); }); describe('finding the layer that is using the current index pattern', () => { @@ -1121,6 +1271,7 @@ describe('IndexPattern Data Source suggestions', () => { }); }); }); + describe('#getDatasourceSuggestionsForVisualizeField', () => { describe('with no layer', () => { function stateWithoutLayer() { @@ -1218,6 +1369,7 @@ describe('IndexPattern Data Source suggestions', () => { columns: { cola: { label: 'My Op 2', + customLabel: true, dataType: 'string', isBucketed: true, @@ -1305,6 +1457,7 @@ describe('IndexPattern Data Source suggestions', () => { columns: { cola: { label: 'My Op', + customLabel: true, dataType: 'number', isBucketed: false, operationType: 'avg', @@ -1316,7 +1469,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - expect(getDatasourceSuggestionsFromCurrentState(state)).toContainEqual( + expect(getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state))).toContainEqual( expect.objectContaining({ table: { isMultiRow: true, @@ -1359,6 +1512,7 @@ describe('IndexPattern Data Source suggestions', () => { columns: { cola: { label: 'My Terms', + customLabel: true, dataType: 'string', isBucketed: true, operationType: 'terms', @@ -1372,6 +1526,7 @@ describe('IndexPattern Data Source suggestions', () => { }, colb: { label: 'My Op', + customLabel: true, dataType: 'number', isBucketed: false, operationType: 'avg', @@ -1383,7 +1538,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - expect(getDatasourceSuggestionsFromCurrentState(state)).toContainEqual( + expect(getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state))).toContainEqual( expect.objectContaining({ table: { isMultiRow: true, @@ -1442,6 +1597,7 @@ describe('IndexPattern Data Source suggestions', () => { }, colb: { label: 'My Op', + customLabel: true, dataType: 'number', isBucketed: true, operationType: 'range', @@ -1487,6 +1643,7 @@ describe('IndexPattern Data Source suggestions', () => { }, colb: { label: 'My Custom Range', + customLabel: true, dataType: 'string', isBucketed: true, operationType: 'range', @@ -1503,7 +1660,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - expect(getDatasourceSuggestionsFromCurrentState(state)).toContainEqual( + expect(getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state))).toContainEqual( expect.objectContaining({ table: { changeType: 'extended', @@ -1555,6 +1712,7 @@ describe('IndexPattern Data Source suggestions', () => { columns: { id1: { label: 'My Op', + customLabel: true, dataType: 'number', isBucketed: false, operationType: 'avg', @@ -1631,6 +1789,7 @@ describe('IndexPattern Data Source suggestions', () => { columns: { col1: { label: 'My Op', + customLabel: true, dataType: 'string', isBucketed: true, @@ -1644,6 +1803,7 @@ describe('IndexPattern Data Source suggestions', () => { }, col2: { label: 'My Op', + customLabel: true, dataType: 'string', isBucketed: true, @@ -1657,6 +1817,7 @@ describe('IndexPattern Data Source suggestions', () => { }, col3: { label: 'My Op', + customLabel: true, dataType: 'string', isBucketed: true, @@ -1670,6 +1831,7 @@ describe('IndexPattern Data Source suggestions', () => { }, col4: { label: 'My Op', + customLabel: true, dataType: 'number', isBucketed: false, @@ -1678,6 +1840,7 @@ describe('IndexPattern Data Source suggestions', () => { }, col5: { label: 'My Op', + customLabel: true, dataType: 'number', isBucketed: false, @@ -1691,31 +1854,26 @@ describe('IndexPattern Data Source suggestions', () => { }; const suggestions = getDatasourceSuggestionsFromCurrentState(state); - // 1 bucket col, 2 metric cols - isTableWithBucketColumns(suggestions[0], ['col1', 'col4', 'col5'], 1); + + // 3 bucket cols, 2 metric cols + isTableWithBucketColumns(suggestions[0], ['col1', 'col2', 'col3', 'col4', 'col5'], 3); // 1 bucket col, 1 metric col isTableWithBucketColumns(suggestions[1], ['col1', 'col4'], 1); // 2 bucket cols, 2 metric cols - isTableWithBucketColumns(suggestions[2], ['col1', 'col2', 'col4', 'col5'], 2); - - // 2 bucket cols, 1 metric col - isTableWithBucketColumns(suggestions[3], ['col1', 'col2', 'col4'], 2); - - // 3 bucket cols, 2 metric cols - isTableWithBucketColumns(suggestions[4], ['col1', 'col2', 'col3', 'col4', 'col5'], 3); + isTableWithBucketColumns(suggestions[2], ['col1', 'col2', 'col4'], 2); // 3 bucket cols, 1 metric col - isTableWithBucketColumns(suggestions[5], ['col1', 'col2', 'col3', 'col4'], 3); + isTableWithBucketColumns(suggestions[3], ['col1', 'col2', 'col3', 'col4'], 3); // first metric col - isTableWithMetricColumns(suggestions[6], ['col4']); + isTableWithMetricColumns(suggestions[4], ['col4']); // second metric col - isTableWithMetricColumns(suggestions[7], ['col5']); + isTableWithMetricColumns(suggestions[5], ['col5']); - expect(suggestions.length).toBe(8); + expect(suggestions.length).toBe(6); }); it('returns an only metric version of a given table', () => { @@ -1770,7 +1928,7 @@ describe('IndexPattern Data Source suggestions', () => { ...initialState.layers.first, columns: { id1: { - label: 'Date histogram', + label: 'field2', dataType: 'date', isBucketed: true, @@ -1794,8 +1952,19 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const suggestions = getDatasourceSuggestionsFromCurrentState(state); - expect(suggestions[1].table.columns[0].operation.label).toBe('Average of field1'); + const suggestions = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + expect(suggestions).toContainEqual( + expect.objectContaining({ + table: expect.objectContaining({ + changeType: 'reduced', + columns: [ + expect.objectContaining({ + operation: expect.objectContaining({ label: 'Average of field1' }), + }), + ], + }), + }) + ); }); it('returns an alternative metric for an only-metric table', () => { @@ -1848,9 +2017,18 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const suggestions = getDatasourceSuggestionsFromCurrentState(state); - expect(suggestions[0].table.columns.length).toBe(1); - expect(suggestions[0].table.columns[0].operation.label).toBe('Sum of field1'); + const suggestions = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + expect(suggestions).toContainEqual( + expect.objectContaining({ + table: expect.objectContaining({ + columns: [ + expect.objectContaining({ + operation: expect.objectContaining({ label: 'Sum of field1' }), + }), + ], + }), + }) + ); }); it('contains a reordering suggestion when there are exactly 2 buckets', () => { @@ -1909,7 +2087,7 @@ describe('IndexPattern Data Source suggestions', () => { ); }); - it('does not generate suggestions if invalid fields are referenced', () => { + it('will generate suggestions even if there are errors from missing fields', () => { const initialState = testInitialState(); const state: IndexPatternPrivateState = { indexPatternRefs: [], @@ -1937,8 +2115,259 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const suggestions = getDatasourceSuggestionsFromCurrentState(state); - expect(suggestions).toEqual([]); + const suggestions = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + expect(suggestions).toContainEqual( + expect.objectContaining({ + table: { + changeType: 'unchanged', + columns: [ + { + columnId: 'col1', + operation: { + dataType: 'string', + isBucketed: true, + label: 'My Op', + scale: undefined, + }, + }, + { + columnId: 'col2', + operation: { + dataType: 'string', + isBucketed: true, + label: 'Top 5', + scale: undefined, + }, + }, + ], + isMultiRow: true, + label: undefined, + layerId: 'first', + }, + }) + ); + }); + + describe('references', () => { + it('will extend the table with a date when starting in an invalid state', () => { + const initialState = testInitialState(); + const state: IndexPatternPrivateState = { + ...initialState, + layers: { + ...initialState.layers, + first: { + ...initialState.layers.first, + columnOrder: ['metric', 'ref', 'ref2'], + columns: { + metric: { + label: '', + dataType: 'number', + isBucketed: false, + operationType: 'count', + sourceField: 'Records', + }, + ref: { + label: '', + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + references: ['metric'], + }, + ref2: { + label: '', + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + references: ['metric2'], + }, + }, + }, + }, + }; + + const result = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + + expect(result).toContainEqual( + expect.objectContaining({ + table: expect.objectContaining({ + changeType: 'extended', + layerId: 'first', + columns: [ + { + columnId: 'id1', + operation: { + dataType: 'date', + isBucketed: true, + label: 'timestampLabel', + scale: 'interval', + }, + }, + { + columnId: 'ref', + operation: { + dataType: 'number', + isBucketed: false, + label: 'Cumulative sum of Records', + scale: undefined, + }, + }, + { + columnId: 'ref2', + operation: { + dataType: 'number', + isBucketed: false, + label: 'Cumulative sum of (incomplete)', + scale: undefined, + }, + }, + ], + }), + keptLayerIds: ['first'], + }) + ); + }); + + it('will make an unchanged suggestion including incomplete references', () => { + const initialState = testInitialState(); + const state: IndexPatternPrivateState = { + ...initialState, + layers: { + ...initialState.layers, + first: { + ...initialState.layers.first, + columnOrder: ['date', 'ref', 'ref2'], + columns: { + date: { + label: '', + dataType: 'date', + isBucketed: true, + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { interval: 'auto' }, + }, + ref: { + label: '', + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + references: ['metric'], + }, + ref2: { + label: '', + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + references: ['metric'], + }, + }, + }, + }, + }; + + const result = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + + expect(result).toContainEqual( + expect.objectContaining({ + table: expect.objectContaining({ + changeType: 'unchanged', + layerId: 'first', + columns: [ + { + columnId: 'date', + operation: { + dataType: 'date', + isBucketed: true, + label: '', + scale: undefined, + }, + }, + { + columnId: 'ref', + operation: { + dataType: 'number', + isBucketed: false, + label: '', + scale: undefined, + }, + }, + { + columnId: 'ref2', + operation: { + dataType: 'number', + isBucketed: false, + label: '', + scale: undefined, + }, + }, + ], + }), + keptLayerIds: ['first'], + }) + ); + }); + + it('will skip a reduced suggestion when handling multiple references', () => { + const initialState = testInitialState(); + const state: IndexPatternPrivateState = { + ...initialState, + layers: { + ...initialState.layers, + first: { + ...initialState.layers.first, + columnOrder: ['date', 'metric', 'metric2', 'ref', 'ref2'], + + columns: { + date: { + label: '', + dataType: 'date', + isBucketed: true, + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { interval: 'auto' }, + }, + metric: { + label: '', + dataType: 'number', + isBucketed: false, + operationType: 'count', + sourceField: 'Records', + }, + ref: { + label: '', + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + references: ['metric'], + }, + metric2: { + label: '', + dataType: 'number', + isBucketed: false, + operationType: 'count', + sourceField: 'Records', + }, + ref2: { + label: '', + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + references: ['metric2'], + }, + }, + }, + }, + }; + + const result = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + + expect(result).not.toContainEqual( + expect.objectContaining({ + table: expect.objectContaining({ + changeType: 'reduced', + }), + }) + ); + }); }); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index ebac396210a5c..969324c67e909 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import _, { partition } from 'lodash'; +import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { generateId } from '../id_generator'; import { DatasourceSuggestion, TableChangeType } from '../types'; @@ -17,8 +17,10 @@ import { operationDefinitionMap, IndexPatternColumn, OperationType, + getExistingColumnGroups, + isReferenced, } from './operations'; -import { hasField, hasInvalidColumns } from './utils'; +import { hasField } from './utils'; import { IndexPattern, IndexPatternPrivateState, @@ -27,7 +29,7 @@ import { } from './types'; import { documentField } from './document_field'; -type IndexPatternSugestion = DatasourceSuggestion; +export type IndexPatternSuggestion = DatasourceSuggestion; function buildSuggestion({ state, @@ -71,10 +73,13 @@ function buildSuggestion({ }, table: { - columns: columnOrder.map((columnId) => ({ - columnId, - operation: columnToOperation(columnMap[columnId]), - })), + columns: columnOrder + // Hide any referenced columns from what visualizations know about + .filter((columnId) => !isReferenced(layers[layerId]!, columnId)) + .map((columnId) => ({ + columnId, + operation: columnToOperation(columnMap[columnId]), + })), isMultiRow, layerId, changeType, @@ -89,8 +94,7 @@ export function getDatasourceSuggestionsForField( state: IndexPatternPrivateState, indexPatternId: string, field: IndexPatternField -): IndexPatternSugestion[] { - if (hasInvalidColumns(state)) return []; +): IndexPatternSuggestion[] { const layers = Object.keys(state.layers); const layerIds = layers.filter((id) => state.layers[id].indexPatternId === indexPatternId); @@ -123,7 +127,7 @@ export function getDatasourceSuggestionsForVisualizeField( state: IndexPatternPrivateState, indexPatternId: string, fieldName: string -): IndexPatternSugestion[] { +): IndexPatternSuggestion[] { const layers = Object.keys(state.layers); const layerIds = layers.filter((id) => state.layers[id].indexPatternId === indexPatternId); // Identify the field by the indexPatternId and the fieldName @@ -158,7 +162,7 @@ function getExistingLayerSuggestionsForField( const fieldInUse = Object.values(layer.columns).some( (column) => hasField(column) && column.sourceField === field.name ); - const suggestions: IndexPatternSugestion[] = []; + const suggestions: IndexPatternSuggestion[] = []; if (usableAsBucketOperation && !fieldInUse) { if ( @@ -221,8 +225,9 @@ function getExistingLayerSuggestionsForField( ); } - const [, metrics] = separateBucketColumns(layer); - if (metrics.length === 1) { + const [, metrics, references] = getExistingColumnGroups(layer); + // TODO: Write test for the case where we have exactly one metric and one reference. We shouldn't switch the inner metric. + if (metrics.length === 1 && references.length === 0) { const layerWithReplacedMetric = replaceColumn({ layer, indexPattern, @@ -257,7 +262,7 @@ function getEmptyLayerSuggestionsForField( layerId: string, indexPatternId: string, field: IndexPatternField -): IndexPatternSugestion[] { +): IndexPatternSuggestion[] { const indexPattern = state.indexPatterns[indexPatternId]; let newLayer: IndexPatternLayer | undefined; const bucketOperation = getBucketOperation(field); @@ -331,7 +336,6 @@ function createNewLayerWithMetricAggregation( export function getDatasourceSuggestionsFromCurrentState( state: IndexPatternPrivateState ): Array> { - if (hasInvalidColumns(state)) return []; const layers = Object.entries(state.layers || {}); if (layers.length > 1) { // Return suggestions that reduce the data to each layer individually @@ -372,12 +376,13 @@ export function getDatasourceSuggestionsFromCurrentState( }), ]); } + return _.flatten( Object.entries(state.layers || {}) .filter(([_id, layer]) => layer.columnOrder.length && layer.indexPatternId) .map(([layerId, layer]) => { const indexPattern = state.indexPatterns[layer.indexPatternId]; - const [buckets, metrics] = separateBucketColumns(layer); + const [buckets, metrics, references] = getExistingColumnGroups(layer); const timeDimension = layer.columnOrder.find( (columnId) => layer.columns[columnId].isBucketed && layer.columns[columnId].dataType === 'date' @@ -390,29 +395,22 @@ export function getDatasourceSuggestionsFromCurrentState( buckets.some((columnId) => layer.columns[columnId].dataType === 'number'); const suggestions: Array> = []; - if (metrics.length === 0) { - // intermediary chart without metric, don't try to suggest reduced versions - suggestions.push( - buildSuggestion({ - state, - layerId, - changeType: 'unchanged', - }) - ); - } else if (buckets.length === 0) { + + // Always suggest an unchanged table, including during invalid states + suggestions.push( + buildSuggestion({ + state, + layerId, + changeType: 'unchanged', + }) + ); + + if (!references.length && metrics.length && buckets.length === 0) { if (timeField) { // suggest current metric over time if there is a default time field suggestions.push(createSuggestionWithDefaultDateHistogram(state, layerId, timeField)); } suggestions.push(...createAlternativeMetricSuggestions(indexPattern, layerId, state)); - // also suggest simple current state - suggestions.push( - buildSuggestion({ - state, - layerId, - changeType: 'unchanged', - }) - ); } else { suggestions.push(...createSimplifiedTableSuggestions(state, layerId)); @@ -570,7 +568,11 @@ function createSuggestionWithDefaultDateHistogram( function createSimplifiedTableSuggestions(state: IndexPatternPrivateState, layerId: string) { const layer = state.layers[layerId]; - const [availableBucketedColumns, availableMetricColumns] = separateBucketColumns(layer); + const [ + availableBucketedColumns, + availableMetricColumns, + availableReferenceColumns, + ] = getExistingColumnGroups(layer); return _.flatten( availableBucketedColumns.map((_col, index) => { @@ -581,21 +583,23 @@ function createSimplifiedTableSuggestions(state: IndexPatternPrivateState, layer columnOrder: [...bucketedColumns, ...availableMetricColumns], }; - if (availableMetricColumns.length > 1) { - return [ - allMetricsSuggestion, - { ...layer, columnOrder: [...bucketedColumns, availableMetricColumns[0]] }, - ]; + if (availableReferenceColumns.length) { + // Don't remove buckets when dealing with any refs. This can break refs. + return []; + } else if (availableMetricColumns.length > 1) { + return [{ ...layer, columnOrder: [...bucketedColumns, availableMetricColumns[0]] }]; } else { return allMetricsSuggestion; } }) ) .concat( - availableMetricColumns.map((columnId) => { - // build suggestions with only metrics - return { ...layer, columnOrder: [columnId] }; - }) + availableReferenceColumns.length + ? [] + : availableMetricColumns.map((columnId) => { + // build suggestions with only metrics + return { ...layer, columnOrder: [columnId] }; + }) ) .map((updatedLayer) => { return buildSuggestion({ @@ -623,7 +627,3 @@ function getMetricSuggestionTitle(layer: IndexPatternLayer, onlyMetric: boolean) 'Title of a suggested chart containing only a single numerical metric calculated over all available data', }); } - -function separateBucketColumns(layer: IndexPatternLayer) { - return partition(layer.columnOrder, (columnId) => layer.columns[columnId].isBucketed); -} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts index ff900134df9a1..6d7a0117a1770 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts @@ -42,6 +42,7 @@ export const { getErrorMessages, isReferenced, resetIncomplete, + isOperationAllowedAsReference, } = actualHelpers; export const { adjustTimeScaleLabelSuffix, DEFAULT_TIME_SCALE } = actualTimeScaleUtils; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx index 0cfba4cfc739f..4fd045c17740d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx @@ -9,6 +9,7 @@ import { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from '. import { IndexPatternLayer } from '../../../types'; import { buildLabelFunction, + getErrorsForDateReference, checkForDateHistogram, dateBasedOperationToExpression, hasDateField, @@ -52,15 +53,18 @@ export const counterRateOperation: OperationDefinition< validateMetadata: (meta) => meta.dataType === 'number' && !meta.isBucketed, }, ], - getPossibleOperation: () => { - return { - dataType: 'number', - isBucketed: false, - scale: 'ratio', - }; + getPossibleOperation: (indexPattern) => { + if (hasDateField(indexPattern)) { + return { + dataType: 'number', + isBucketed: false, + scale: 'ratio', + }; + } }, getDefaultLabel: (column, indexPattern, columns) => { - return ofName(columns[column.references[0]]?.label, column.timeScale); + const ref = columns[column.references[0]]; + return ofName(ref && 'sourceField' in ref ? ref.sourceField : undefined, column.timeScale); }, toExpression: (layer, columnId) => { return dateBasedOperationToExpression(layer, columnId, 'lens_counter_rate'); @@ -69,7 +73,7 @@ export const counterRateOperation: OperationDefinition< const metric = layer.columns[referenceIds[0]]; const timeScale = previousColumn?.timeScale || DEFAULT_TIME_SCALE; return { - label: ofName(metric?.label, timeScale), + label: ofName(metric && 'sourceField' in metric ? metric.sourceField : undefined, timeScale), dataType: 'number', operationType: 'counter_rate', isBucketed: false, @@ -88,13 +92,22 @@ export const counterRateOperation: OperationDefinition< isTransferable: (column, newIndexPattern) => { return hasDateField(newIndexPattern); }, - getErrorMessage: (layer: IndexPatternLayer) => { - return checkForDateHistogram( + getErrorMessage: (layer: IndexPatternLayer, columnId: string) => { + return getErrorsForDateReference( layer, + columnId, i18n.translate('xpack.lens.indexPattern.counterRate', { defaultMessage: 'Counter rate', }) ); }, + getDisabledStatus(indexPattern, layer) { + return checkForDateHistogram( + layer, + i18n.translate('xpack.lens.indexPattern.counterRate', { + defaultMessage: 'Counter rate', + }) + )?.join(', '); + }, timeScalingMode: 'mandatory', }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx index 9244aaaf90ab7..7067b6470bec7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx @@ -7,12 +7,17 @@ import { i18n } from '@kbn/i18n'; import { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from '../column_types'; import { IndexPatternLayer } from '../../../types'; -import { checkForDateHistogram, dateBasedOperationToExpression } from './utils'; +import { + checkForDateHistogram, + getErrorsForDateReference, + dateBasedOperationToExpression, + hasDateField, +} from './utils'; import { OperationDefinition } from '..'; const ofName = (name?: string) => { return i18n.translate('xpack.lens.indexPattern.cumulativeSumOf', { - defaultMessage: 'Cumulative sum rate of {name}', + defaultMessage: 'Cumulative sum of {name}', values: { name: name ?? @@ -46,23 +51,26 @@ export const cumulativeSumOperation: OperationDefinition< validateMetadata: (meta) => meta.dataType === 'number' && !meta.isBucketed, }, ], - getPossibleOperation: () => { - return { - dataType: 'number', - isBucketed: false, - scale: 'ratio', - }; + getPossibleOperation: (indexPattern) => { + if (hasDateField(indexPattern)) { + return { + dataType: 'number', + isBucketed: false, + scale: 'ratio', + }; + } }, getDefaultLabel: (column, indexPattern, columns) => { - return ofName(columns[column.references[0]]?.label); + const ref = columns[column.references[0]]; + return ofName(ref && 'sourceField' in ref ? ref.sourceField : undefined); }, toExpression: (layer, columnId) => { return dateBasedOperationToExpression(layer, columnId, 'cumulative_sum'); }, buildColumn: ({ referenceIds, previousColumn, layer }) => { - const metric = layer.columns[referenceIds[0]]; + const ref = layer.columns[referenceIds[0]]; return { - label: ofName(metric?.label), + label: ofName(ref && 'sourceField' in ref ? ref.sourceField : undefined), dataType: 'number', operationType: 'cumulative_sum', isBucketed: false, @@ -80,12 +88,21 @@ export const cumulativeSumOperation: OperationDefinition< isTransferable: () => { return true; }, - getErrorMessage: (layer: IndexPatternLayer) => { - return checkForDateHistogram( + getErrorMessage: (layer: IndexPatternLayer, columnId: string) => { + return getErrorsForDateReference( layer, + columnId, i18n.translate('xpack.lens.indexPattern.cumulativeSum', { defaultMessage: 'Cumulative sum', }) ); }, + getDisabledStatus(indexPattern, layer) { + return checkForDateHistogram( + layer, + i18n.translate('xpack.lens.indexPattern.cumulativeSum', { + defaultMessage: 'Cumulative sum', + }) + )?.join(', '); + }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/derivative.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/derivative.tsx index 41fe361c7ba9c..358046ad5bfb9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/derivative.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/derivative.tsx @@ -10,6 +10,7 @@ import { IndexPatternLayer } from '../../../types'; import { buildLabelFunction, checkForDateHistogram, + getErrorsForDateReference, dateBasedOperationToExpression, hasDateField, } from './utils'; @@ -51,23 +52,29 @@ export const derivativeOperation: OperationDefinition< validateMetadata: (meta) => meta.dataType === 'number' && !meta.isBucketed, }, ], - getPossibleOperation: () => { - return { - dataType: 'number', - isBucketed: false, - scale: 'ratio', - }; + getPossibleOperation: (indexPattern) => { + if (hasDateField(indexPattern)) { + return { + dataType: 'number', + isBucketed: false, + scale: 'ratio', + }; + } }, getDefaultLabel: (column, indexPattern, columns) => { - return ofName(columns[column.references[0]]?.label, column.timeScale); + const ref = columns[column.references[0]]; + return ofName(ref && 'sourceField' in ref ? ref.sourceField : undefined, column.timeScale); }, toExpression: (layer, columnId) => { return dateBasedOperationToExpression(layer, columnId, 'derivative'); }, buildColumn: ({ referenceIds, previousColumn, layer }) => { - const metric = layer.columns[referenceIds[0]]; + const ref = layer.columns[referenceIds[0]]; return { - label: ofName(metric?.label, previousColumn?.timeScale), + label: ofName( + ref && 'sourceField' in ref ? ref.sourceField : undefined, + previousColumn?.timeScale + ), dataType: 'number', operationType: 'derivative', isBucketed: false, @@ -87,13 +94,22 @@ export const derivativeOperation: OperationDefinition< return hasDateField(newIndexPattern); }, onOtherColumnChanged: adjustTimeScaleOnOtherColumnChange, - getErrorMessage: (layer: IndexPatternLayer) => { - return checkForDateHistogram( + getErrorMessage: (layer: IndexPatternLayer, columnId: string) => { + return getErrorsForDateReference( layer, + columnId, i18n.translate('xpack.lens.indexPattern.derivative', { defaultMessage: 'Differences', }) ); }, + getDisabledStatus(indexPattern, layer) { + return checkForDateHistogram( + layer, + i18n.translate('xpack.lens.indexPattern.derivative', { + defaultMessage: 'Differences', + }) + )?.join(', '); + }, timeScalingMode: 'optional', }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx index 59d5924b9a370..e1bc378635f1d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx @@ -14,6 +14,7 @@ import { IndexPatternLayer } from '../../../types'; import { buildLabelFunction, checkForDateHistogram, + getErrorsForDateReference, dateBasedOperationToExpression, hasDateField, } from './utils'; @@ -50,7 +51,7 @@ export const movingAverageOperation: OperationDefinition< type: 'moving_average', priority: 1, displayName: i18n.translate('xpack.lens.indexPattern.movingAverage', { - defaultMessage: 'Moving Average', + defaultMessage: 'Moving average', }), input: 'fullReference', selectionStyle: 'full', @@ -60,12 +61,14 @@ export const movingAverageOperation: OperationDefinition< validateMetadata: (meta) => meta.dataType === 'number' && !meta.isBucketed, }, ], - getPossibleOperation: () => { - return { - dataType: 'number', - isBucketed: false, - scale: 'ratio', - }; + getPossibleOperation: (indexPattern) => { + if (hasDateField(indexPattern)) { + return { + dataType: 'number', + isBucketed: false, + scale: 'ratio', + }; + } }, getDefaultLabel: (column, indexPattern, columns) => { return ofName(columns[column.references[0]]?.label, column.timeScale); @@ -99,17 +102,39 @@ export const movingAverageOperation: OperationDefinition< return hasDateField(newIndexPattern); }, onOtherColumnChanged: adjustTimeScaleOnOtherColumnChange, - getErrorMessage: (layer: IndexPatternLayer) => { - return checkForDateHistogram( + getErrorMessage: (layer: IndexPatternLayer, columnId: string) => { + return getErrorsForDateReference( layer, + columnId, i18n.translate('xpack.lens.indexPattern.movingAverage', { - defaultMessage: 'Moving Average', + defaultMessage: 'Moving average', }) ); }, + getDisabledStatus(indexPattern, layer) { + return checkForDateHistogram( + layer, + i18n.translate('xpack.lens.indexPattern.movingAverage', { + defaultMessage: 'Moving average', + }) + )?.join(', '); + }, timeScalingMode: 'optional', }; +function isValidNumber(input: string) { + if (input === '') return false; + try { + const val = parseFloat(input); + if (isNaN(val)) return false; + if (val < 1) return false; + if (val.toString().includes('.')) return false; + } catch (e) { + return false; + } + return true; +} + function MovingAverageParamEditor({ layer, updateLayer, @@ -120,10 +145,8 @@ function MovingAverageParamEditor({ useDebounceWithOptions( () => { - if (inputValue === '') { - return; - } - const inputNumber = Number(inputValue); + if (!isValidNumber(inputValue)) return; + const inputNumber = parseInt(inputValue, 10); updateLayer( updateColumnParam({ layer, @@ -137,6 +160,7 @@ function MovingAverageParamEditor({ 256, [inputValue] ); + return ( ) => setInputValue(e.target.value)} + min={1} + step={1} + isInvalid={!isValidNumber(inputValue)} /> ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.test.ts new file mode 100644 index 0000000000000..403f2b87ac86e --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.test.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { checkReferences } from './utils'; +import { operationDefinitionMap } from '..'; +import { createMockedReferenceOperation } from '../../mocks'; + +// Mock prevents issue with circular loading +jest.mock('..'); + +describe('utils', () => { + beforeEach(() => { + // @ts-expect-error test-only operation type + operationDefinitionMap.testReference = createMockedReferenceOperation(); + }); + + describe('checkReferences', () => { + it('should show an error if the reference is missing', () => { + expect( + checkReferences( + { + columns: { + ref: { + label: 'Label', + // @ts-expect-error test-only operation type + operationType: 'testReference', + isBucketed: false, + dataType: 'number', + references: ['missing'], + }, + }, + columnOrder: ['ref'], + indexPatternId: '', + }, + 'ref' + ) + ).toEqual(['"Label" is not fully configured']); + }); + + it('should show an error if the reference is not allowed per the requirements', () => { + expect( + checkReferences( + { + columns: { + ref: { + label: 'Label', + // @ts-expect-error test-only operation type + operationType: 'testReference', + isBucketed: false, + dataType: 'number', + references: ['invalid'], + }, + invalid: { + label: 'Date', + operationType: 'date_histogram', + isBucketed: true, + dataType: 'date', + sourceField: 'timestamp', + params: { interval: 'auto' }, + }, + }, + columnOrder: ['invalid', 'ref'], + indexPatternId: '', + }, + 'ref' + ) + ).toEqual(['Dimension "Label" is configured incorrectly']); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts index bac45f683e444..ca4b7c53b7ec7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts @@ -5,11 +5,13 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunctionAST } from '@kbn/interpreter/common'; -import { TimeScaleUnit } from '../../../time_scale'; -import { IndexPattern, IndexPatternLayer } from '../../../types'; +import type { ExpressionFunctionAST } from '@kbn/interpreter/common'; +import type { TimeScaleUnit } from '../../../time_scale'; +import type { IndexPattern, IndexPatternLayer } from '../../../types'; import { adjustTimeScaleLabelSuffix } from '../../time_scale_utils'; -import { ReferenceBasedIndexPatternColumn } from '../column_types'; +import type { ReferenceBasedIndexPatternColumn } from '../column_types'; +import { operationDefinitionMap } from '..'; +import type { IndexPatternColumn, RequiredReference } from '..'; export const buildLabelFunction = (ofName: (name?: string) => string) => ( name?: string, @@ -41,6 +43,78 @@ export function checkForDateHistogram(layer: IndexPatternLayer, name: string) { ]; } +export function checkReferences(layer: IndexPatternLayer, columnId: string) { + const column = layer.columns[columnId] as ReferenceBasedIndexPatternColumn; + + const errors: string[] = []; + + column.references.forEach((referenceId, index) => { + if (!layer.columns[referenceId]) { + errors.push( + i18n.translate('xpack.lens.indexPattern.missingReferenceError', { + defaultMessage: '"{dimensionLabel}" is not fully configured', + values: { + dimensionLabel: column.label, + }, + }) + ); + } else { + const referenceColumn = layer.columns[referenceId]!; + const definition = operationDefinitionMap[column.operationType]; + if (definition.input !== 'fullReference') { + throw new Error('inconsistent state - column is not a reference operation'); + } + const requirements = definition.requiredReferences[index]; + const isValid = isColumnValidAsReference({ + validation: requirements, + column: referenceColumn, + }); + + if (!isValid) { + errors.push( + i18n.translate('xpack.lens.indexPattern.invalidReferenceConfiguration', { + defaultMessage: 'Dimension "{dimensionLabel}" is configured incorrectly', + values: { + dimensionLabel: column.label, + }, + }) + ); + } + } + }); + return errors.length ? errors : undefined; +} + +export function isColumnValidAsReference({ + column, + validation, +}: { + column: IndexPatternColumn; + validation: RequiredReference; +}): boolean { + if (!column) return false; + const operationType = column.operationType; + const operationDefinition = operationDefinitionMap[operationType]; + return ( + validation.input.includes(operationDefinition.input) && + (!validation.specificOperations || validation.specificOperations.includes(operationType)) && + validation.validateMetadata(column) + ); +} + +export function getErrorsForDateReference( + layer: IndexPatternLayer, + columnId: string, + name: string +) { + const dateErrors = checkForDateHistogram(layer, name) ?? []; + const referenceErrors = checkReferences(layer, columnId) ?? []; + if (dateErrors.length || referenceErrors.length) { + return [...dateErrors, ...referenceErrors]; + } + return; +} + export function hasDateField(indexPattern: IndexPattern) { return indexPattern.fields.some((field) => field.type === 'date'); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx index 95e905f6021be..970f56020c7cd 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx @@ -10,7 +10,7 @@ import { buildExpressionFunction } from '../../../../../../../src/plugins/expres import { OperationDefinition } from './index'; import { FormattedIndexPatternColumn, FieldBasedIndexPatternColumn } from './column_types'; -import { getInvalidFieldMessage } from './helpers'; +import { getInvalidFieldMessage, getSafeName } from './helpers'; const supportedTypes = new Set(['string', 'boolean', 'number', 'ip', 'date']); @@ -21,7 +21,9 @@ const IS_BUCKETED = false; function ofName(name: string) { return i18n.translate('xpack.lens.indexPattern.cardinalityOf', { defaultMessage: 'Unique count of {name}', - values: { name }, + values: { + name, + }, }); } @@ -58,8 +60,7 @@ export const cardinalityOperation: OperationDefinition - ofName(indexPattern.getFieldByName(column.sourceField)!.displayName), + getDefaultLabel: (column, indexPattern) => ofName(getSafeName(column.sourceField, indexPattern)), buildColumn({ field, previousColumn }) { return { label: ofName(field.displayName), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx index 0d8ed44f528a8..06d330a4a7eb2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx @@ -69,7 +69,12 @@ export const countOperation: OperationDefinition + adjustTimeScaleOnOtherColumnChange( + layer, + thisColumnId, + changedColumnId + ), toEsAggsFn: (column, columnId) => { return buildExpressionFunction('aggCount', { id: columnId, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx index eadcf8384b1dd..8c2fa43b541d4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx @@ -689,4 +689,32 @@ describe('date_histogram', () => { expect(instance.find('[data-test-subj="lensDateHistogramValue"]').exists()).toBeFalsy(); }); }); + + describe('getDefaultLabel', () => { + it('should not throw when the source field is not located', () => { + expect( + dateHistogramOperation.getDefaultLabel( + { + label: '', + dataType: 'date', + isBucketed: true, + operationType: 'date_histogram', + sourceField: 'missing', + params: { interval: 'auto' }, + }, + indexPattern1, + { + col1: { + label: '', + dataType: 'date', + isBucketed: true, + operationType: 'date_histogram', + sourceField: 'missing', + params: { interval: 'auto' }, + }, + } + ) + ).toEqual('Missing field'); + }); + }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index cdd1ccad96a99..a41cc88c4f292 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -28,7 +28,7 @@ import { search, } from '../../../../../../../src/plugins/data/public'; import { buildExpressionFunction } from '../../../../../../../src/plugins/expressions/public'; -import { getInvalidFieldMessage } from './helpers'; +import { getInvalidFieldMessage, getSafeName } from './helpers'; const { isValidInterval } = search.aggs; const autoInterval = 'auto'; @@ -67,8 +67,7 @@ export const dateHistogramOperation: OperationDefinition< }; } }, - getDefaultLabel: (column, indexPattern) => - indexPattern.getFieldByName(column.sourceField)!.displayName, + getDefaultLabel: (column, indexPattern) => getSafeName(column.sourceField, indexPattern), buildColumn({ field }) { let interval = autoInterval; let timeZone: string | undefined; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.test.ts new file mode 100644 index 0000000000000..04e04816d98ef --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createMockedIndexPattern } from '../../mocks'; +import { getInvalidFieldMessage } from './helpers'; + +describe('helpers', () => { + describe('getInvalidFieldMessage', () => { + it('return an error if a field was removed', () => { + const messages = getInvalidFieldMessage( + { + dataType: 'number', + isBucketed: false, + label: 'Foo', + operationType: 'count', // <= invalid + sourceField: 'bytes', + }, + createMockedIndexPattern() + ); + expect(messages).toHaveLength(1); + expect(messages![0]).toEqual('Field bytes was not found'); + }); + + it('returns an error if a field is the wrong type', () => { + const messages = getInvalidFieldMessage( + { + dataType: 'number', + isBucketed: false, + label: 'Foo', + operationType: 'avg', // <= invalid + sourceField: 'timestamp', + }, + createMockedIndexPattern() + ); + expect(messages).toHaveLength(1); + expect(messages![0]).toEqual('Field timestamp was not found'); + }); + + it('returns no message if all fields are matching', () => { + const messages = getInvalidFieldMessage( + { + dataType: 'number', + isBucketed: false, + label: 'Foo', + operationType: 'avg', + sourceField: 'bytes', + }, + createMockedIndexPattern() + ); + expect(messages).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx index 640a357d9a7a4..7b96bcf4f2069 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx @@ -62,3 +62,12 @@ export function getInvalidFieldMessage( ] : undefined; } + +export function getSafeName(name: string, indexPattern: IndexPattern): string { + const field = indexPattern.getFieldByName(name); + return field + ? field.displayName + : i18n.translate('xpack.lens.indexPattern.missingFieldLabel', { + defaultMessage: 'Missing field', + }); +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 6431dac7b381d..6231460347de2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -152,8 +152,9 @@ interface BaseOperationDefinitionProps { * return an updated column. If not implemented, the `id` function is used instead. */ onOtherColumnChanged?: ( - currentColumn: C, - columns: Partial> + layer: IndexPatternLayer, + thisColumnId: string, + changedColumnId: string ) => C; /** * React component for operation specific settings shown in the popover editor @@ -176,7 +177,7 @@ interface BaseOperationDefinitionProps { * but disable it from usage, this function returns the string describing * the status. Otherwise it returns undefined */ - getDisabledStatus?: (indexPattern: IndexPattern) => string | undefined; + getDisabledStatus?: (indexPattern: IndexPattern, layer: IndexPatternLayer) => string | undefined; /** * Validate that the operation has the right preconditions in the state. For example: * @@ -314,9 +315,9 @@ interface FullReferenceOperationDefinition { ) => ReferenceBasedIndexPatternColumn & C; /** * Returns the meta data of the operation if applied. Undefined - * if the field is not applicable. + * if the operation can't be added with these fields. */ - getPossibleOperation: () => OperationMetadata; + getPossibleOperation: (indexPattern: IndexPattern) => OperationMetadata | undefined; /** * A chain of expression functions which will transform the table */ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx index 817958aee5490..bde8cd0e42427 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx @@ -311,13 +311,13 @@ describe('last_value', () => { it('should return disabledStatus if indexPattern does contain date field', () => { const indexPattern = createMockedIndexPattern(); - expect(lastValueOperation.getDisabledStatus!(indexPattern)).toEqual(undefined); + expect(lastValueOperation.getDisabledStatus!(indexPattern, layer)).toEqual(undefined); const indexPatternWithoutTimeFieldName = { ...indexPattern, timeFieldName: undefined, }; - expect(lastValueOperation.getDisabledStatus!(indexPatternWithoutTimeFieldName)).toEqual( + expect(lastValueOperation.getDisabledStatus!(indexPatternWithoutTimeFieldName, layer)).toEqual( undefined ); @@ -326,7 +326,10 @@ describe('last_value', () => { fields: indexPattern.fields.filter((f) => f.type !== 'date'), }; - const disabledStatus = lastValueOperation.getDisabledStatus!(indexPatternWithoutTimefields); + const disabledStatus = lastValueOperation.getDisabledStatus!( + indexPatternWithoutTimefields, + layer + ); expect(disabledStatus).toEqual( 'This function requires the presence of a date field in your index' ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx index 7b5aee860654a..256ef7f75676d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx @@ -13,12 +13,14 @@ import { FieldBasedIndexPatternColumn } from './column_types'; import { IndexPatternField, IndexPattern } from '../../types'; import { updateColumnParam } from '../layer_helpers'; import { DataType } from '../../../types'; -import { getInvalidFieldMessage } from './helpers'; +import { getInvalidFieldMessage, getSafeName } from './helpers'; function ofName(name: string) { return i18n.translate('xpack.lens.indexPattern.lastValueOf', { defaultMessage: 'Last value of {name}', - values: { name }, + values: { + name, + }, }); } @@ -87,8 +89,7 @@ export const lastValueOperation: OperationDefinition - indexPattern.getFieldByName(column.sourceField)!.displayName, + getDefaultLabel: (column, indexPattern) => ofName(getSafeName(column.sourceField, indexPattern)), input: 'field', onFieldChange: (oldColumn, field) => { const newParams = { ...oldColumn.params }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index a886bfdaad325..eb25b5d932b1f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { buildExpressionFunction } from '../../../../../../../src/plugins/expressions/public'; import { OperationDefinition } from './index'; -import { getInvalidFieldMessage } from './helpers'; +import { getInvalidFieldMessage, getSafeName } from './helpers'; import { FormattedIndexPatternColumn, FieldBasedIndexPatternColumn, @@ -45,11 +45,11 @@ function buildMetricOperation>({ optionalTimeScaling?: boolean; }) { const labelLookup = (name: string, column?: BaseIndexPatternColumn) => { - const rawLabel = ofName(name); + const label = ofName(name); if (!optionalTimeScaling) { - return rawLabel; + return label; } - return adjustTimeScaleLabelSuffix(rawLabel, undefined, column?.timeScale); + return adjustTimeScaleLabelSuffix(label, undefined, column?.timeScale); }; return { @@ -81,10 +81,12 @@ function buildMetricOperation>({ (!newField.aggregationRestrictions || newField.aggregationRestrictions![type]) ); }, - onOtherColumnChanged: (column, otherColumns) => - optionalTimeScaling ? adjustTimeScaleOnOtherColumnChange(column, otherColumns) : column, + onOtherColumnChanged: (layer, thisColumnId, changedColumnId) => + optionalTimeScaling + ? adjustTimeScaleOnOtherColumnChange(layer, thisColumnId, changedColumnId) + : layer.columns[thisColumnId], getDefaultLabel: (column, indexPattern, columns) => - labelLookup(indexPattern.getFieldByName(column.sourceField)!.displayName, column), + labelLookup(getSafeName(column.sourceField, indexPattern), column), buildColumn: ({ field, previousColumn }) => ({ label: labelLookup(field.displayName, previousColumn), dataType: 'number', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index 2ba8f5febce5b..b687e6fe3da50 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -98,7 +98,10 @@ export const rangeOperation: OperationDefinition - indexPattern.getFieldByName(column.sourceField)!.displayName, + indexPattern.getFieldByName(column.sourceField)?.displayName ?? + i18n.translate('xpack.lens.indexPattern.missingFieldLabel', { + defaultMessage: 'Missing field', + }), buildColumn({ field }) { return { label: field.displayName, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index 888df40873a35..8462d374c6e6b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -18,23 +18,36 @@ import { } from '@elastic/eui'; import { AggFunctionsMapping } from '../../../../../../../../src/plugins/data/public'; import { buildExpressionFunction } from '../../../../../../../../src/plugins/expressions/public'; -import { IndexPatternColumn } from '../../../indexpattern'; import { updateColumnParam, isReferenced } from '../../layer_helpers'; import { DataType } from '../../../../types'; import { OperationDefinition } from '../index'; import { FieldBasedIndexPatternColumn } from '../column_types'; import { ValuesRangeInput } from './values_range_input'; import { getInvalidFieldMessage } from '../helpers'; +import type { IndexPatternLayer } from '../../../types'; -function ofName(name: string) { +function ofName(name?: string) { return i18n.translate('xpack.lens.indexPattern.termsOf', { defaultMessage: 'Top values of {name}', - values: { name }, + values: { + name: + name ?? + i18n.translate('xpack.lens.indexPattern.missingFieldLabel', { + defaultMessage: 'Missing field', + }), + }, }); } -function isSortableByColumn(column: IndexPatternColumn) { - return !column.isBucketed && column.operationType !== 'last_value'; +function isSortableByColumn(layer: IndexPatternLayer, columnId: string) { + const column = layer.columns[columnId]; + return ( + column && + !column.isBucketed && + column.operationType !== 'last_value' && + !('references' in column) && + !isReferenced(layer, columnId) + ); } const DEFAULT_SIZE = 3; @@ -89,10 +102,7 @@ export const termsOperation: OperationDefinition - column && !isReferenced(layer, columnId) && isSortableByColumn(column) - ) + .filter(([columnId]) => isSortableByColumn(layer, columnId)) .map(([id]) => id)[0]; const previousBucketsLength = Object.values(layer.columns).filter( @@ -138,7 +148,7 @@ export const termsOperation: OperationDefinition - ofName(indexPattern.getFieldByName(column.sourceField)!.displayName), + ofName(indexPattern.getFieldByName(column.sourceField)?.displayName), onFieldChange: (oldColumn, field) => { const newParams = { ...oldColumn.params }; if ('format' in newParams && field.type !== 'number') { @@ -152,11 +162,13 @@ export const termsOperation: OperationDefinition { + onOtherColumnChanged: (layer, thisColumnId, changedColumnId) => { + const columns = layer.columns; + const currentColumn = columns[thisColumnId] as TermsIndexPatternColumn; if (currentColumn.params.orderBy.type === 'column') { // check whether the column is still there and still a metric const columnSortedBy = columns[currentColumn.params.orderBy.columnId]; - if (!columnSortedBy || !isSortableByColumn(columnSortedBy)) { + if (!columnSortedBy || !isSortableByColumn(layer, changedColumnId)) { return { ...currentColumn, params: { @@ -194,7 +206,7 @@ export const termsOperation: OperationDefinition isSortableByColumn(column)) + .filter(([sortId]) => isSortableByColumn(layer, sortId)) .map(([sortId, column]) => { return { value: toValue({ type: 'column', columnId: sortId }), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index eb78bb3ffebff..0209c0b9a448b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -402,15 +402,25 @@ describe('terms', () => { }, sourceField: 'category', }; - const updatedColumn = termsOperation.onOtherColumnChanged!(initialColumn, { - col1: { - label: 'Count', - dataType: 'number', - isBucketed: false, - sourceField: 'Records', - operationType: 'count', + const updatedColumn = termsOperation.onOtherColumnChanged!( + { + indexPatternId: '', + columnOrder: [], + columns: { + col2: initialColumn, + col1: { + label: 'Count', + dataType: 'number', + isBucketed: false, + sourceField: 'Records', + operationType: 'count', + }, + }, }, - }); + 'col2', + 'col1' + ); + expect(updatedColumn).toBe(initialColumn); }); @@ -429,18 +439,74 @@ describe('terms', () => { }, sourceField: 'category', }; - const updatedColumn = termsOperation.onOtherColumnChanged!(initialColumn, { - col1: { - label: 'Last Value', - dataType: 'number', - isBucketed: false, - sourceField: 'bytes', - operationType: 'last_value', - params: { - sortField: 'time', + const updatedColumn = termsOperation.onOtherColumnChanged!( + { + columns: { + col2: initialColumn, + col1: { + label: 'Last Value', + dataType: 'number', + isBucketed: false, + sourceField: 'bytes', + operationType: 'last_value', + params: { + sortField: 'time', + }, + }, }, + columnOrder: [], + indexPatternId: '', }, - }); + 'col2', + 'col1' + ); + expect(updatedColumn.params).toEqual( + expect.objectContaining({ + orderBy: { type: 'alphabetical' }, + }) + ); + }); + + it('should switch to alphabetical ordering if metric is reference-based', () => { + const initialColumn: TermsIndexPatternColumn = { + label: 'Top value of category', + dataType: 'string', + isBucketed: true, + + // Private + operationType: 'terms', + params: { + orderBy: { type: 'column', columnId: 'col1' }, + size: 3, + orderDirection: 'asc', + }, + sourceField: 'category', + }; + const updatedColumn = termsOperation.onOtherColumnChanged!( + { + columns: { + col2: initialColumn, + col1: { + label: 'Cumulative sum', + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + references: ['referenced'], + }, + referenced: { + label: '', + dataType: 'number', + isBucketed: false, + operationType: 'count', + sourceField: 'Records', + }, + }, + columnOrder: [], + indexPatternId: '', + }, + 'col2', + 'col1' + ); expect(updatedColumn.params).toEqual( expect.objectContaining({ orderBy: { type: 'alphabetical' }, @@ -451,20 +517,27 @@ describe('terms', () => { it('should switch to alphabetical ordering if there are no columns to order by', () => { const termsColumn = termsOperation.onOtherColumnChanged!( { - label: 'Top value of category', - dataType: 'string', - isBucketed: true, + columns: { + col2: { + label: 'Top value of category', + dataType: 'string', + isBucketed: true, - // Private - operationType: 'terms', - params: { - orderBy: { type: 'column', columnId: 'col1' }, - size: 3, - orderDirection: 'asc', + // Private + operationType: 'terms', + params: { + orderBy: { type: 'column', columnId: 'col1' }, + size: 3, + orderDirection: 'asc', + }, + sourceField: 'category', + }, }, - sourceField: 'category', + columnOrder: [], + indexPatternId: '', }, - {} + 'col2', + 'col1' ); expect(termsColumn.params).toEqual( expect.objectContaining({ @@ -476,33 +549,39 @@ describe('terms', () => { it('should switch to alphabetical ordering if the order column is not a metric anymore', () => { const termsColumn = termsOperation.onOtherColumnChanged!( { - label: 'Top value of category', - dataType: 'string', - isBucketed: true, + columns: { + col2: { + label: 'Top value of category', + dataType: 'string', + isBucketed: true, - // Private - operationType: 'terms', - params: { - orderBy: { type: 'column', columnId: 'col1' }, - size: 3, - orderDirection: 'asc', - }, - sourceField: 'category', - }, - { - col1: { - label: 'Value of timestamp', - dataType: 'date', - isBucketed: true, + // Private + operationType: 'terms', + params: { + orderBy: { type: 'column', columnId: 'col1' }, + size: 3, + orderDirection: 'asc', + }, + sourceField: 'category', + }, + col1: { + label: 'Value of timestamp', + dataType: 'date', + isBucketed: true, - // Private - operationType: 'date_histogram', - params: { - interval: 'w', + // Private + operationType: 'date_histogram', + params: { + interval: 'w', + }, + sourceField: 'timestamp', }, - sourceField: 'timestamp', }, - } + columnOrder: [], + indexPatternId: '', + }, + 'col2', + 'col1' ); expect(termsColumn.params).toEqual( expect.objectContaining({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts index 7123becf71b4d..079913347470a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts @@ -12,6 +12,7 @@ export { IndexPatternColumn, FieldBasedIndexPatternColumn, IncompleteColumn, + RequiredReference, } from './definitions'; export { createMockedReferenceOperation } from './mocks'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index bb09474798fd4..9496f95f74dec 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -190,6 +190,44 @@ describe('state_helpers', () => { ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2'] })); }); + it('should insert a metric after buckets, but before references', () => { + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Date histogram of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + col3: { + label: 'Reference', + dataType: 'number', + isBucketed: false, + + operationType: 'cumulative_sum', + references: ['col2'], + }, + }, + }; + expect( + insertNewColumn({ + layer, + indexPattern, + columnId: 'col2', + op: 'count', + field: documentField, + }) + ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2', 'col3'] })); + }); + it('should insert new buckets at the end of previous buckets', () => { const layer: IndexPatternLayer = { indexPatternId: '1', @@ -782,18 +820,83 @@ describe('state_helpers', () => { field: indexPattern.fields[2], // bytes field }); - expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith(termsColumn, { - col1: termsColumn, - col2: expect.objectContaining({ - label: 'Average of bytes', - dataType: 'number', - isBucketed: false, + expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith( + { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: termsColumn, + col2: expect.objectContaining({ + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + sourceField: 'bytes', + operationType: 'avg', + }), + }, + incompleteColumns: {}, + }, + 'col1', + 'col2' + ); + }); - // Private - operationType: 'avg', - sourceField: 'bytes', - }), + it('should execute adjustments for other columns when creating a reference', () => { + const termsColumn: TermsIndexPatternColumn = { + label: 'Top values of source', + dataType: 'string', + isBucketed: true, + + // Private + operationType: 'terms', + sourceField: 'source', + params: { + orderBy: { type: 'column', columnId: 'willBeReference' }, + orderDirection: 'desc', + size: 5, + }, + }; + + replaceColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1', 'willBeReference'], + columns: { + col1: termsColumn, + willBeReference: { + label: 'Count', + dataType: 'number', + isBucketed: false, + sourceField: 'Records', + operationType: 'count', + }, + }, + }, + indexPattern, + columnId: 'willBeReference', + op: 'cumulative_sum', }); + + expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith( + { + indexPatternId: '1', + columnOrder: ['col1', 'willBeReference'], + columns: { + col1: { + ...termsColumn, + params: { orderBy: { type: 'alphabetical' }, orderDirection: 'asc', size: 5 }, + }, + willBeReference: expect.objectContaining({ + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + }), + }, + incompleteColumns: {}, + }, + 'col1', + 'willBeReference' + ); }); it('should not wrap the previous operation when switching to reference', () => { @@ -963,7 +1066,7 @@ describe('state_helpers', () => { isTransferable: jest.fn(), toExpression: jest.fn().mockReturnValue([]), getPossibleOperation: jest.fn().mockReturnValue({ dataType: 'number', isBucketed: false }), - getDefaultLabel: () => 'Test reference', + getDefaultLabel: jest.fn().mockReturnValue('Test reference'), }; const layer: IndexPatternLayer = { @@ -1081,6 +1184,7 @@ describe('state_helpers', () => { }, }, columnId: 'col1', + indexPattern, }) ).toEqual({ indexPatternId: '1', @@ -1126,6 +1230,7 @@ describe('state_helpers', () => { }, }, columnId: 'col2', + indexPattern, }) ).toEqual({ indexPatternId: '1', @@ -1176,11 +1281,14 @@ describe('state_helpers', () => { }, }, columnId: 'col2', + indexPattern, }); - expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith(termsColumn, { - col1: termsColumn, - }); + expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith( + { indexPatternId: '1', columnOrder: ['col1', 'col2'], columns: { col1: termsColumn } }, + 'col1', + 'col2' + ); }); it('should delete the column and all of its references', () => { @@ -1207,11 +1315,57 @@ describe('state_helpers', () => { }, }, }; - expect(deleteColumn({ layer, columnId: 'col2' })).toEqual( + expect(deleteColumn({ layer, columnId: 'col2', indexPattern })).toEqual( expect.objectContaining({ columnOrder: [], columns: {} }) ); }); + it('should update the labels when deleting columns', () => { + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: { + label: 'Count', + dataType: 'number', + isBucketed: false, + + operationType: 'count', + sourceField: 'Records', + }, + col2: { + label: 'Changed label', + dataType: 'number', + isBucketed: false, + + // @ts-expect-error not a valid type + operationType: 'testReference', + references: ['col1'], + }, + }, + }; + deleteColumn({ layer, columnId: 'col1', indexPattern }); + expect(operationDefinitionMap.testReference.getDefaultLabel).toHaveBeenCalledWith( + { + label: 'Changed label', + dataType: 'number', + isBucketed: false, + operationType: 'testReference', + references: ['col1'], + }, + indexPattern, + { + col2: { + label: 'Default label', + dataType: 'number', + isBucketed: false, + operationType: 'testReference', + references: ['col1'], + }, + } + ); + }); + it('should recursively delete references', () => { const layer: IndexPatternLayer = { indexPatternId: '1', @@ -1245,7 +1399,7 @@ describe('state_helpers', () => { }, }, }; - expect(deleteColumn({ layer, columnId: 'col3' })).toEqual( + expect(deleteColumn({ layer, columnId: 'col3', indexPattern })).toEqual( expect.objectContaining({ columnOrder: [], columns: {} }) ); }); @@ -1680,63 +1834,34 @@ describe('state_helpers', () => { }); describe('getErrorMessages', () => { - it('should collect errors from the operation definitions', () => { + it('should collect errors from metric-type operation definitions', () => { const mock = jest.fn().mockReturnValue(['error 1']); - operationDefinitionMap.testReference.getErrorMessage = mock; + operationDefinitionMap.avg.getErrorMessage = mock; const errors = getErrorMessages({ indexPatternId: '1', columnOrder: [], columns: { - col1: - // @ts-expect-error not statically analyzed - { operationType: 'testReference', references: [] }, + // @ts-expect-error invalid column + col1: { operationType: 'avg' }, }, }); expect(mock).toHaveBeenCalled(); expect(errors).toHaveLength(1); }); - it('should identify missing references', () => { + it('should collect errors from reference-type operation definitions', () => { + const mock = jest.fn().mockReturnValue(['error 1']); + operationDefinitionMap.testReference.getErrorMessage = mock; const errors = getErrorMessages({ indexPatternId: '1', columnOrder: [], columns: { col1: - // @ts-expect-error not statically analyzed yet - { operationType: 'testReference', references: ['ref1', 'ref2'] }, - }, - }); - expect(errors).toHaveLength(2); - }); - - it('should identify references that are no longer valid', () => { - // There is only one operation with `none` as the input type - // @ts-expect-error this function is not valid - operationDefinitionMap.testReference.requiredReferences = [ - { - input: ['none'], - validateMetadata: () => true, - }, - ]; - - const errors = getErrorMessages({ - indexPatternId: '1', - columnOrder: [], - columns: { - // @ts-expect-error incomplete operation - ref1: { - dataType: 'string', - isBucketed: true, - operationType: 'terms', - }, - col1: { - label: '', - references: ['ref1'], - // @ts-expect-error tests only - operationType: 'testReference', - }, + // @ts-expect-error not statically analyzed + { operationType: 'testReference', references: [] }, }, }); + expect(mock).toHaveBeenCalled(); expect(errors).toHaveLength(1); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 1619ad907fffc..2d8078b9a6154 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -5,7 +5,6 @@ */ import _, { partition } from 'lodash'; -import { i18n } from '@kbn/i18n'; import { operationDefinitionMap, operationDefinitions, @@ -61,9 +60,15 @@ export function insertNewColumn({ const possibleOperation = operationDefinition.getPossibleOperation(); const isBucketed = Boolean(possibleOperation.isBucketed); if (isBucketed) { - return addBucket(layer, operationDefinition.buildColumn({ ...baseOptions, layer }), columnId); + return updateDefaultLabels( + addBucket(layer, operationDefinition.buildColumn({ ...baseOptions, layer }), columnId), + indexPattern + ); } else { - return addMetric(layer, operationDefinition.buildColumn({ ...baseOptions, layer }), columnId); + return updateDefaultLabels( + addMetric(layer, operationDefinition.buildColumn({ ...baseOptions, layer }), columnId), + indexPattern + ); } } @@ -77,7 +82,7 @@ export function insertNewColumn({ // access to the operationSupportMatrix, we should validate the metadata against // the possible fields const validOperations = Object.values(operationDefinitionMap).filter(({ type }) => - isOperationAllowedAsReference({ validation, operationType: type }) + isOperationAllowedAsReference({ validation, operationType: type, indexPattern }) ); if (!validOperations.length) { @@ -122,29 +127,23 @@ export function insertNewColumn({ return newId; }); - const possibleOperation = operationDefinition.getPossibleOperation(); - const isBucketed = Boolean(possibleOperation.isBucketed); - if (isBucketed) { - return addBucket( - tempLayer, - operationDefinition.buildColumn({ - ...baseOptions, - layer: tempLayer, - referenceIds, - }), - columnId + const possibleOperation = operationDefinition.getPossibleOperation(indexPattern); + if (!possibleOperation) { + throw new Error( + `Can't create operation ${op} because it's incompatible with the index pattern` ); - } else { - return addMetric( + } + const isBucketed = Boolean(possibleOperation.isBucketed); + + const addOperationFn = isBucketed ? addBucket : addMetric; + return updateDefaultLabels( + addOperationFn( tempLayer, - operationDefinition.buildColumn({ - ...baseOptions, - layer: tempLayer, - referenceIds, - }), + operationDefinition.buildColumn({ ...baseOptions, layer: tempLayer, referenceIds }), columnId - ); - } + ), + indexPattern + ); } const invalidFieldName = (layer.incompleteColumns ?? {})[columnId]?.sourceField; @@ -159,16 +158,22 @@ export function insertNewColumn({ } const isBucketed = Boolean(possibleOperation.isBucketed); if (isBucketed) { - return addBucket( - layer, - operationDefinition.buildColumn({ ...baseOptions, layer, field: invalidField }), - columnId + return updateDefaultLabels( + addBucket( + layer, + operationDefinition.buildColumn({ ...baseOptions, layer, field: invalidField }), + columnId + ), + indexPattern ); } else { - return addMetric( - layer, - operationDefinition.buildColumn({ ...baseOptions, layer, field: invalidField }), - columnId + return updateDefaultLabels( + addMetric( + layer, + operationDefinition.buildColumn({ ...baseOptions, layer, field: invalidField }), + columnId + ), + indexPattern ); } } else if (!field) { @@ -193,19 +198,15 @@ export function insertNewColumn({ }; } const isBucketed = Boolean(possibleOperation.isBucketed); - if (isBucketed) { - return addBucket( + const addOperationFn = isBucketed ? addBucket : addMetric; + return updateDefaultLabels( + addOperationFn( layer, operationDefinition.buildColumn({ ...baseOptions, layer, field }), columnId - ); - } else { - return addMetric( - layer, - operationDefinition.buildColumn({ ...baseOptions, layer, field }), - columnId - ); - } + ), + indexPattern + ); } export function replaceColumn({ @@ -241,39 +242,50 @@ export function replaceColumn({ if (previousDefinition.input === 'fullReference') { (previousColumn as ReferenceBasedIndexPatternColumn).references.forEach((id: string) => { - tempLayer = deleteColumn({ layer: tempLayer, columnId: id }); + tempLayer = deleteColumn({ layer: tempLayer, columnId: id, indexPattern }); }); } + tempLayer = resetIncomplete(tempLayer, columnId); + if (operationDefinition.input === 'fullReference') { const referenceIds = operationDefinition.requiredReferences.map(() => generateId()); - const newColumns = { - ...tempLayer.columns, - [columnId]: operationDefinition.buildColumn({ - ...baseOptions, - layer: tempLayer, - referenceIds, - previousColumn, - }), - }; - return { + const newLayer = { ...tempLayer, - columnOrder: getColumnOrder({ ...tempLayer, columns: newColumns }), - columns: newColumns, + columns: { + ...tempLayer.columns, + [columnId]: operationDefinition.buildColumn({ + ...baseOptions, + layer: tempLayer, + referenceIds, + previousColumn, + }), + }, }; + return updateDefaultLabels( + { + ...tempLayer, + columnOrder: getColumnOrder(newLayer), + columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), + }, + indexPattern + ); } if (operationDefinition.input === 'none') { let newColumn = operationDefinition.buildColumn({ ...baseOptions, layer: tempLayer }); newColumn = adjustLabel(newColumn, previousColumn); - const newColumns = { ...tempLayer.columns, [columnId]: newColumn }; - return { - ...tempLayer, - columnOrder: getColumnOrder({ ...tempLayer, columns: newColumns }), - columns: adjustColumnReferencesForChangedColumn(newColumns, columnId), - }; + const newLayer = { ...tempLayer, columns: { ...tempLayer.columns, [columnId]: newColumn } }; + return updateDefaultLabels( + { + ...tempLayer, + columnOrder: getColumnOrder(newLayer), + columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), + }, + indexPattern + ); } if (!field) { @@ -289,12 +301,15 @@ export function replaceColumn({ let newColumn = operationDefinition.buildColumn({ ...baseOptions, layer: tempLayer, field }); newColumn = adjustLabel(newColumn, previousColumn); - const newColumns = { ...tempLayer.columns, [columnId]: newColumn }; - return { - ...tempLayer, - columnOrder: getColumnOrder({ ...tempLayer, columns: newColumns }), - columns: adjustColumnReferencesForChangedColumn(newColumns, columnId), - }; + const newLayer = { ...tempLayer, columns: { ...tempLayer.columns, [columnId]: newColumn } }; + return updateDefaultLabels( + { + ...tempLayer, + columnOrder: getColumnOrder(newLayer), + columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), + }, + indexPattern + ); } else if ( operationDefinition.input === 'field' && field && @@ -304,12 +319,20 @@ export function replaceColumn({ // Same operation, new field const newColumn = operationDefinition.onFieldChange(previousColumn, field); - const newColumns = { ...layer.columns, [columnId]: adjustLabel(newColumn, previousColumn) }; - return { - ...layer, - columnOrder: getColumnOrder({ ...layer, columns: newColumns }), - columns: adjustColumnReferencesForChangedColumn(newColumns, columnId), - }; + if (previousColumn.customLabel) { + newColumn.customLabel = true; + newColumn.label = previousColumn.label; + } + + const newLayer = { ...layer, columns: { ...layer.columns, [columnId]: newColumn } }; + return updateDefaultLabels( + { + ...resetIncomplete(layer, columnId), + columnOrder: getColumnOrder(newLayer), + columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), + }, + indexPattern + ); } else { throw new Error('nothing changed'); } @@ -370,7 +393,6 @@ function addMetric( ...layer.columns, [addedColumnId]: column, }, - columnOrder: [...layer.columnOrder, addedColumnId], }; return { ...tempLayer, columnOrder: getColumnOrder(tempLayer) }; } @@ -409,17 +431,18 @@ export function updateColumnParam({ }; } -function adjustColumnReferencesForChangedColumn( - columns: Record, - columnId: string -) { - const newColumns = { ...columns }; +function adjustColumnReferencesForChangedColumn(layer: IndexPatternLayer, changedColumnId: string) { + const newColumns = { ...layer.columns }; Object.keys(newColumns).forEach((currentColumnId) => { - if (currentColumnId !== columnId) { + if (currentColumnId !== changedColumnId) { const currentColumn = newColumns[currentColumnId]; const operationDefinition = operationDefinitionMap[currentColumn.operationType]; newColumns[currentColumnId] = operationDefinition.onOtherColumnChanged - ? operationDefinition.onOtherColumnChanged(currentColumn, newColumns) + ? operationDefinition.onOtherColumnChanged( + { ...layer, columns: newColumns }, + currentColumnId, + changedColumnId + ) : currentColumn; } }); @@ -429,9 +452,11 @@ function adjustColumnReferencesForChangedColumn( export function deleteColumn({ layer, columnId, + indexPattern, }: { layer: IndexPatternLayer; columnId: string; + indexPattern: IndexPattern; }): IndexPatternLayer { const column = layer.columns[columnId]; if (!column) { @@ -451,17 +476,27 @@ export function deleteColumn({ let newLayer = { ...layer, - columns: adjustColumnReferencesForChangedColumn(hypotheticalColumns, columnId), + columns: adjustColumnReferencesForChangedColumn( + { ...layer, columns: hypotheticalColumns }, + columnId + ), }; extraDeletions.forEach((id) => { - newLayer = deleteColumn({ layer: newLayer, columnId: id }); + newLayer = deleteColumn({ layer: newLayer, columnId: id, indexPattern }); }); const newIncomplete = { ...(newLayer.incompleteColumns || {}) }; delete newIncomplete[columnId]; - return { ...newLayer, columnOrder: getColumnOrder(newLayer), incompleteColumns: newIncomplete }; + return updateDefaultLabels( + { + ...newLayer, + columnOrder: getColumnOrder(newLayer), + incompleteColumns: newIncomplete, + }, + indexPattern + ); } // Derives column order from column object, respects existing columnOrder @@ -482,7 +517,7 @@ export function getColumnOrder(layer: IndexPatternLayer): string[] { const [direct, referenceBased] = _.partition( entries, - ([id, col]) => operationDefinitionMap[col.operationType].input !== 'fullReference' + ([, col]) => operationDefinitionMap[col.operationType].input !== 'fullReference' ); // If a reference has another reference as input, put it last in sort order referenceBased.sort(([idA, a], [idB, b]) => { @@ -503,7 +538,7 @@ export function getColumnOrder(layer: IndexPatternLayer): string[] { } // Splits existing columnOrder into the three categories -function getExistingColumnGroups(layer: IndexPatternLayer): [string[], string[], string[]] { +export function getExistingColumnGroups(layer: IndexPatternLayer): [string[], string[], string[]] { const [direct, referenced] = partition( layer.columnOrder, (columnId) => layer.columns[columnId] && !('references' in layer.columns[columnId]) @@ -553,44 +588,9 @@ export function getErrorMessages(layer: IndexPatternLayer): string[] | undefined Object.entries(layer.columns).forEach(([columnId, column]) => { const def = operationDefinitionMap[column.operationType]; - if (def.input === 'fullReference' && def.getErrorMessage) { + if (def.getErrorMessage) { errors.push(...(def.getErrorMessage(layer, columnId) ?? [])); } - - if ('references' in column) { - column.references.forEach((referenceId, index) => { - if (!layer.columns[referenceId]) { - errors.push( - i18n.translate('xpack.lens.indexPattern.missingReferenceError', { - defaultMessage: 'Dimension {dimensionLabel} is incomplete', - values: { - dimensionLabel: column.label, - }, - }) - ); - } else { - const referenceColumn = layer.columns[referenceId]!; - const requirements = - // @ts-expect-error not statically analyzed - operationDefinitionMap[column.operationType].requiredReferences[index]; - const isValid = isColumnValidAsReference({ - validation: requirements, - column: referenceColumn, - }); - - if (!isValid) { - errors.push( - i18n.translate('xpack.lens.indexPattern.invalidReferenceConfiguration', { - defaultMessage: 'Dimension {dimensionLabel} does not have a valid configuration', - values: { - dimensionLabel: column.label, - }, - }) - ); - } - } - }); - } }); return errors.length ? errors : undefined; @@ -603,30 +603,15 @@ export function isReferenced(layer: IndexPatternLayer, columnId: string): boolea return allReferences.includes(columnId); } -function isColumnValidAsReference({ - column, - validation, -}: { - column: IndexPatternColumn; - validation: RequiredReference; -}): boolean { - if (!column) return false; - const operationType = column.operationType; - const operationDefinition = operationDefinitionMap[operationType]; - return ( - validation.input.includes(operationDefinition.input) && - (!validation.specificOperations || validation.specificOperations.includes(operationType)) && - validation.validateMetadata(column) - ); -} - -function isOperationAllowedAsReference({ +export function isOperationAllowedAsReference({ operationType, validation, field, + indexPattern, }: { operationType: OperationType; validation: RequiredReference; + indexPattern: IndexPattern; field?: IndexPatternField; }): boolean { const operationDefinition = operationDefinitionMap[operationType]; @@ -635,9 +620,12 @@ function isOperationAllowedAsReference({ if (field && operationDefinition.input === 'field') { const metadata = operationDefinition.getPossibleOperationForField(field); hasValidMetadata = Boolean(metadata) && validation.validateMetadata(metadata!); - } else if (operationDefinition.input !== 'field') { + } else if (operationDefinition.input === 'none') { const metadata = operationDefinition.getPossibleOperation(); hasValidMetadata = Boolean(metadata) && validation.validateMetadata(metadata!); + } else if (operationDefinition.input === 'fullReference') { + const metadata = operationDefinition.getPossibleOperation(indexPattern); + hasValidMetadata = Boolean(metadata) && validation.validateMetadata(metadata!); } else { // TODO: How can we validate the metadata without a specific field? } @@ -648,6 +636,29 @@ function isOperationAllowedAsReference({ ); } +// Labels need to be updated when columns are added because reference-based column labels +// are sometimes copied into the parents +function updateDefaultLabels( + layer: IndexPatternLayer, + indexPattern: IndexPattern +): IndexPatternLayer { + const copiedColumns = { ...layer.columns }; + layer.columnOrder.forEach((id) => { + const col = copiedColumns[id]; + if (!col.customLabel) { + copiedColumns[id] = { + ...col, + label: operationDefinitionMap[col.operationType].getDefaultLabel( + col, + indexPattern, + copiedColumns + ), + }; + } + }); + return { ...layer, columns: copiedColumns }; +} + export function resetIncomplete(layer: IndexPatternLayer, columnId: string): IndexPatternLayer { const incompleteColumns = { ...(layer.incompleteColumns ?? {}) }; delete incompleteColumns[columnId]; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts index 58685fa494a04..c111983ea2cd6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts @@ -167,10 +167,13 @@ export function getAvailableOperationsByMetadata(indexPattern: IndexPattern) { operationDefinition.getPossibleOperation() ); } else if (operationDefinition.input === 'fullReference') { - addToMap( - { type: 'fullReference', operationType: operationDefinition.type }, - operationDefinition.getPossibleOperation() - ); + const validOperation = operationDefinition.getPossibleOperation(indexPattern); + if (validOperation) { + addToMap( + { type: 'fullReference', operationType: operationDefinition.type }, + validOperation + ); + } } }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.test.ts index 841011c588433..09132b142986f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.test.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TimeScaleUnit } from '../time_scale'; -import { IndexPatternColumn } from './definitions'; +import type { IndexPatternLayer } from '../types'; +import type { TimeScaleUnit } from '../time_scale'; +import type { IndexPatternColumn } from './definitions'; import { adjustTimeScaleLabelSuffix, adjustTimeScaleOnOtherColumnChange } from './time_scale_utils'; export const DEFAULT_TIME_SCALE = 's' as TimeScaleUnit; @@ -48,45 +49,71 @@ describe('time scale utils', () => { isBucketed: false, timeScale: 's', }; + const baseLayer: IndexPatternLayer = { + columns: { col1: baseColumn }, + columnOrder: [], + indexPatternId: '', + }; it('should keep column if there is no time scale', () => { const column = { ...baseColumn, timeScale: undefined }; - expect(adjustTimeScaleOnOtherColumnChange(column, { col1: column })).toBe(column); + expect( + adjustTimeScaleOnOtherColumnChange( + { ...baseLayer, columns: { col1: column } }, + 'col1', + 'col2' + ) + ).toBe(column); }); it('should keep time scale if there is a date histogram', () => { expect( - adjustTimeScaleOnOtherColumnChange(baseColumn, { - col1: baseColumn, - col2: { - operationType: 'date_histogram', - dataType: 'date', - isBucketed: true, - label: '', + adjustTimeScaleOnOtherColumnChange( + { + ...baseLayer, + columns: { + col1: baseColumn, + col2: { + operationType: 'date_histogram', + dataType: 'date', + isBucketed: true, + label: '', + sourceField: 'date', + params: { interval: 'auto' }, + }, + }, }, - }) + 'col1', + 'col2' + ) ).toBe(baseColumn); }); it('should remove time scale if there is no date histogram', () => { - expect(adjustTimeScaleOnOtherColumnChange(baseColumn, { col1: baseColumn })).toHaveProperty( + expect(adjustTimeScaleOnOtherColumnChange(baseLayer, 'col1', 'col2')).toHaveProperty( 'timeScale', undefined ); }); it('should remove suffix from label', () => { - expect(adjustTimeScaleOnOtherColumnChange(baseColumn, { col1: baseColumn })).toHaveProperty( - 'label', - 'Count of records' - ); + expect( + adjustTimeScaleOnOtherColumnChange( + { ...baseLayer, columns: { col1: baseColumn } }, + 'col1', + 'col2' + ) + ).toHaveProperty('label', 'Count of records'); }); it('should keep custom label', () => { const column = { ...baseColumn, label: 'abc', customLabel: true }; - expect(adjustTimeScaleOnOtherColumnChange(column, { col1: column })).toHaveProperty( - 'label', - 'abc' - ); + expect( + adjustTimeScaleOnOtherColumnChange( + { ...baseLayer, columns: { col1: column } }, + 'col1', + 'col2' + ) + ).toHaveProperty('label', 'abc'); }); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.ts index 5d525e573a617..340cad97e7db0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.ts @@ -5,8 +5,9 @@ */ import { unitSuffixesLong } from '../suffix_formatter'; -import { TimeScaleUnit } from '../time_scale'; -import { BaseIndexPatternColumn } from './definitions/column_types'; +import type { TimeScaleUnit } from '../time_scale'; +import type { IndexPatternLayer } from '../types'; +import type { IndexPatternColumn } from './definitions'; export const DEFAULT_TIME_SCALE = 's' as TimeScaleUnit; @@ -30,10 +31,13 @@ export function adjustTimeScaleLabelSuffix( return `${cleanedLabel} ${unitSuffixesLong[newTimeScale]}`; } -export function adjustTimeScaleOnOtherColumnChange( - column: T, - columns: Partial> -) { +export function adjustTimeScaleOnOtherColumnChange( + layer: IndexPatternLayer, + thisColumnId: string, + changedColumnId: string +): T { + const columns = layer.columns; + const column = columns[thisColumnId] as T; if (!column.timeScale) { return column; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts index 702930d02a90e..57cc4abeb723a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts @@ -5,7 +5,7 @@ */ import { DataType } from '../types'; -import { IndexPatternPrivateState, IndexPattern, IndexPatternLayer } from './types'; +import { IndexPattern, IndexPatternLayer } from './types'; import { DraggedField } from './indexpattern'; import { BaseIndexPatternColumn, @@ -44,29 +44,6 @@ export function isDraggedField(fieldCandidate: unknown): fieldCandidate is Dragg ); } -export function hasInvalidColumns(state: IndexPatternPrivateState) { - return getInvalidLayers(state).length > 0; -} - -export function getInvalidLayers(state: IndexPatternPrivateState) { - return Object.values(state.layers).filter((layer) => { - return layer.columnOrder.some((columnId) => - isColumnInvalid(layer, columnId, state.indexPatterns[layer.indexPatternId]) - ); - }); -} - -export function getInvalidColumnsForLayer( - layers: IndexPatternLayer[], - indexPatternMap: Record -) { - return layers.map((layer) => { - return layer.columnOrder.filter((columnId) => - isColumnInvalid(layer, columnId, indexPatternMap[layer.indexPatternId]) - ); - }); -} - export function isColumnInvalid( layer: IndexPatternLayer, columnId: string, diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 92ea9508cf837..c212a371401d9 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -326,6 +326,81 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.lens.getDatatableCellText(0, 1)).to.eql('6,011.351'); }); + it('should create a valid XY chart with references', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'moving_average', + keepOpen: true, + }); + await PageObjects.lens.configureReference({ + operation: 'sum', + field: 'bytes', + }); + await PageObjects.lens.closeDimensionEditor(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'cumulative_sum', + keepOpen: true, + }); + await PageObjects.lens.configureReference({ + field: 'Records', + }); + await PageObjects.lens.closeDimensionEditor(); + + // Two Y axes that are both valid + expect(await find.allByCssSelector('.echLegendItem')).to.have.length(2); + }); + + /** + * The edge cases are: + * + * 1. Showing errors when creating a partial configuration + * 2. Being able to drag in a new field while in partial config + * 3. Being able to switch charts while in partial config + */ + it('should handle edge cases in reference-based operations', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'cumulative_sum', + }); + expect(await PageObjects.lens.getErrorCount()).to.eql(1); + + await PageObjects.lens.removeDimension('lnsXY_xDimensionPanel'); + expect(await PageObjects.lens.getErrorCount()).to.eql(2); + + await PageObjects.lens.dragFieldToDimensionTrigger( + '@timestamp', + 'lnsXY_xDimensionPanel > lns-empty-dimension' + ); + expect(await PageObjects.lens.getErrorCount()).to.eql(1); + + expect(await PageObjects.lens.hasChartSwitchWarning('lnsDatatable')).to.eql(false); + await PageObjects.lens.switchToVisualization('lnsDatatable'); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_metrics')).to.eql( + 'Cumulative sum of (incomplete)' + ); + }); + it('should allow to change index pattern', async () => { await PageObjects.lens.switchFirstLayerIndexPattern('log*'); expect(await PageObjects.lens.getFirstLayerIndexPattern()).to.equal('log*'); diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 2159f939a56f7..7e1fb4ab10a4a 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -122,6 +122,32 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont } }, + /** + * Changes the specified dimension to the specified operation and (optinally) field. + * + * @param opts.dimension - the selector of the dimension being changed + * @param opts.operation - the desired operation ID for the dimension + * @param opts.field - the desired field for the dimension + * @param layerIndex - the index of the layer + */ + async configureReference(opts: { + operation?: string; + field?: string; + isPreviousIncompatible?: boolean; + }) { + if (opts.operation) { + const target = await testSubjects.find('indexPattern-subFunction-selection-row'); + await comboBox.openOptionsList(target); + await comboBox.setElement(target, opts.operation); + } + + if (opts.field) { + const target = await testSubjects.find('indexPattern-reference-field-selection-row'); + await comboBox.openOptionsList(target); + await comboBox.setElement(target, opts.field); + } + }, + /** * Drags field to workspace * @@ -327,6 +353,19 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }); }, + /** Counts the visible warnings in the config panel */ + async getErrorCount() { + const moreButton = await testSubjects.exists('configuration-failure-more-errors'); + if (moreButton) { + await retry.try(async () => { + await testSubjects.click('configuration-failure-more-errors'); + await testSubjects.missingOrFail('configuration-failure-more-errors'); + }); + } + const errors = await testSubjects.findAll('configuration-failure-error'); + return errors?.length ?? 0; + }, + /** * Checks a specific subvisualization in the chart switcher for a "data loss" indicator * From 9a2858105e7f91c836412e2deb35f0f6bb7c6191 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Tue, 22 Dec 2020 09:44:47 -0600 Subject: [PATCH 05/72] [build] Migrate grunt ensureAllTestsInCIGroup to script (#85873) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .ci/jobs.yml | 2 +- scripts/ensure_all_tests_in_ci_group.js | 21 ++++++++ .../dev/run_ensure_all_tests_in_ci_group.js | 49 +++++++++---------- test/scripts/jenkins_build_kibana.sh | 2 +- 4 files changed, 45 insertions(+), 29 deletions(-) create mode 100644 scripts/ensure_all_tests_in_ci_group.js rename tasks/function_test_groups.js => src/dev/run_ensure_all_tests_in_ci_group.js (65%) diff --git a/.ci/jobs.yml b/.ci/jobs.yml index d4ec8a3d5a699..f62ec9510d2d4 100644 --- a/.ci/jobs.yml +++ b/.ci/jobs.yml @@ -1,4 +1,4 @@ -# This file is needed by functionalTests:ensureAllTestsInCiGroup for the list of ciGroups. That must be changed before this file can be removed +# This file is needed by node scripts/ensure_all_tests_in_ci_group for the list of ciGroups. That must be changed before this file can be removed JOB: - kibana-intake diff --git a/scripts/ensure_all_tests_in_ci_group.js b/scripts/ensure_all_tests_in_ci_group.js new file mode 100644 index 0000000000000..d189aac8f62e8 --- /dev/null +++ b/scripts/ensure_all_tests_in_ci_group.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('../src/setup_node_env'); +require('../src/dev/run_ensure_all_tests_in_ci_group'); diff --git a/tasks/function_test_groups.js b/src/dev/run_ensure_all_tests_in_ci_group.js similarity index 65% rename from tasks/function_test_groups.js rename to src/dev/run_ensure_all_tests_in_ci_group.js index 0b456dcb0da13..b5d36c405cbbb 100644 --- a/tasks/function_test_groups.js +++ b/src/dev/run_ensure_all_tests_in_ci_group.js @@ -21,32 +21,28 @@ import { readFileSync } from 'fs'; import { resolve } from 'path'; import execa from 'execa'; -import grunt from 'grunt'; import { safeLoad } from 'js-yaml'; -const JOBS_YAML = readFileSync(resolve(__dirname, '../.ci/jobs.yml'), 'utf8'); +import { run } from '@kbn/dev-utils'; + +const JOBS_YAML = readFileSync(resolve(__dirname, '../../.ci/jobs.yml'), 'utf8'); const TEST_TAGS = safeLoad(JOBS_YAML) .JOB.filter((id) => id.startsWith('kibana-ciGroup')) .map((id) => id.replace(/^kibana-/, '')); -grunt.registerTask( - 'functionalTests:ensureAllTestsInCiGroup', - 'Check that all of the functional tests are in a CI group', - async function () { - const done = this.async(); - - try { - const result = await execa(process.execPath, [ - 'scripts/functional_test_runner', - ...TEST_TAGS.map((tag) => `--include-tag=${tag}`), - '--config', - 'test/functional/config.js', - '--test-stats', - ]); - const stats = JSON.parse(result.stderr); - - if (stats.excludedTests.length > 0) { - grunt.fail.fatal(` +run(async ({ log }) => { + try { + const result = await execa(process.execPath, [ + 'scripts/functional_test_runner', + ...TEST_TAGS.map((tag) => `--include-tag=${tag}`), + '--config', + 'test/functional/config.js', + '--test-stats', + ]); + const stats = JSON.parse(result.stderr); + + if (stats.excludedTests.length > 0) { + log.error(` ${stats.excludedTests.length} tests are excluded by the ciGroup tags, make sure that all test suites have a "ciGroup{X}" tag and that "tasks/functional_test_groups.js" knows about the tag that you are using. @@ -55,12 +51,11 @@ grunt.registerTask( - ${stats.excludedTests.join('\n - ')} `); - return; - } - - done(); - } catch (error) { - grunt.fail.fatal(error.stack); + process.exitCode = 1; + return; } + } catch (error) { + log.error(error.stack); + process.exitCode = 1; } -); +}); diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index f449986713f97..6184708ea3fc6 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -10,7 +10,7 @@ fi export KBN_NP_PLUGINS_BUILT=true echo " -> Ensuring all functional tests are in a ciGroup" -yarn run grunt functionalTests:ensureAllTestsInCiGroup; +node scripts/ensure_all_tests_in_ci_group; # Do not build kibana for code coverage run if [[ -z "$CODE_COVERAGE" ]] ; then From 3aeb344b7c79c07ba1d4f900f0e8032ba78e967a Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Tue, 22 Dec 2020 10:06:37 -0600 Subject: [PATCH 06/72] Enable new chart library setting (#86538) --- src/plugins/vis_type_vislib/public/plugin.ts | 2 +- src/plugins/vis_type_xy/public/plugin.ts | 2 +- src/plugins/vis_type_xy/server/plugin.ts | 2 +- test/functional/config.js | 1 + x-pack/test/functional/config.js | 1 + 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/plugins/vis_type_vislib/public/plugin.ts b/src/plugins/vis_type_vislib/public/plugin.ts index 36a184d3da507..0f849c1833230 100644 --- a/src/plugins/vis_type_vislib/public/plugin.ts +++ b/src/plugins/vis_type_vislib/public/plugin.ts @@ -61,7 +61,7 @@ export class VisTypeVislibPlugin core: VisTypeVislibCoreSetup, { expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies ) { - if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, true)) { + if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, false)) { // Register only non-replaced vis types convertedTypeDefinitions.forEach(visualizations.createBaseVisualization); visualizations.createBaseVisualization(pieVisTypeDefinition); diff --git a/src/plugins/vis_type_xy/public/plugin.ts b/src/plugins/vis_type_xy/public/plugin.ts index 7425c5f7248ac..07084de67fd21 100644 --- a/src/plugins/vis_type_xy/public/plugin.ts +++ b/src/plugins/vis_type_xy/public/plugin.ts @@ -71,7 +71,7 @@ export class VisTypeXyPlugin core: VisTypeXyCoreSetup, { expressions, visualizations, charts }: VisTypeXyPluginSetupDependencies ) { - if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, true)) { + if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, false)) { setUISettings(core.uiSettings); setThemeService(charts.theme); setColorsService(charts.legacyColors); diff --git a/src/plugins/vis_type_xy/server/plugin.ts b/src/plugins/vis_type_xy/server/plugin.ts index b5999535064aa..fafc4052a88fa 100644 --- a/src/plugins/vis_type_xy/server/plugin.ts +++ b/src/plugins/vis_type_xy/server/plugin.ts @@ -31,7 +31,7 @@ export const uiSettingsConfig: Record> = { name: i18n.translate('visTypeXy.advancedSettings.visualization.legacyChartsLibrary.name', { defaultMessage: 'Legacy charts library', }), - value: true, + value: false, description: i18n.translate( 'visTypeXy.advancedSettings.visualization.legacyChartsLibrary.description', { diff --git a/test/functional/config.js b/test/functional/config.js index 5bef9896d17cc..ea6e75b174b4c 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -59,6 +59,7 @@ export default async function ({ readConfigFile }) { defaults: { 'accessibility:disableAnimations': true, 'dateFormat:tz': 'UTC', + 'visualization:visualize:legacyChartsLibrary': true, }, }, diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 814f943a68b05..1815942a06a9a 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -95,6 +95,7 @@ export default async function ({ readConfigFile }) { defaults: { 'accessibility:disableAnimations': true, 'dateFormat:tz': 'UTC', + 'visualization:visualize:legacyChartsLibrary': true, }, }, // the apps section defines the urls that From ce546adb046a390f234f429e517b61f469457b48 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Tue, 22 Dec 2020 08:14:07 -0800 Subject: [PATCH 07/72] [ML] Use doc links service for more ML pages (#86405) --- .../feature_importance/decision_path_popover.tsx | 7 ++----- .../advanced_step/advanced_step_form.tsx | 14 ++++---------- .../classification_exploration/evaluate_panel.tsx | 4 ++-- .../regression_exploration/evaluate_panel.tsx | 4 ++-- .../feature_importance_summary.tsx | 4 ++-- .../components/calendars/description.tsx | 3 +-- .../components/custom_urls/description.tsx | 3 +-- .../components/summary_count_field/description.tsx | 3 +-- 8 files changed, 15 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx b/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx index 40fcd1a6d316e..46982c7553c30 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx @@ -54,7 +54,7 @@ export const DecisionPathPopover: FC = ({ const { services: { docLinks }, } = useMlKibana(); - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; + const docLink = docLinks.links.ml.featureImportance; if (featureImportance.length < 2) { return ; @@ -106,10 +106,7 @@ export const DecisionPathPopover: FC = ({ values={{ predictionFieldName, linkedFeatureImportanceValues: ( - + ); -function getZeroClassesMessage(elasaticUrl: string, version: string) { +function getZeroClassesMessage(elasticUrl: string) { return ( + {i18n.translate('xpack.ml.dataframe.analytics.create.aucRocLabel', { defaultMessage: 'AUC ROC', })} @@ -136,7 +132,7 @@ export const AdvancedStepForm: FC = ({ const { services: { docLinks }, } = useMlKibana(); - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; + const classAucRocDocLink = docLinks.links.ml.classificationAucRoc; const { setEstimatedModelMemoryLimit, setFormState } = actions; const { form, isJobCreated, estimatedModelMemoryLimit } = state; @@ -422,9 +418,7 @@ export const AdvancedStepForm: FC = ({ helpText={getTopClassesHelpText(selectedNumTopClasses)} isInvalid={selectedNumTopClasses === 0 || selectedNumTopClassesIsInvalid} error={[ - ...(selectedNumTopClasses === 0 - ? [getZeroClassesMessage(ELASTIC_WEBSITE_URL, DOC_LINK_VERSION)] - : []), + ...(selectedNumTopClasses === 0 ? [getZeroClassesMessage(classAucRocDocLink)] : []), ...(selectedNumTopClassesIsInvalid ? [numClassesTypeMessage] : []), ]} > diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx index 469ded8dfe4ae..8009cda455e51 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx @@ -272,7 +272,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, se return {columnId === ACTUAL_CLASS_ID ? cellValue : accuracy}; }; - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; + const docLink = docLinks.links.ml.classificationEvaluation; const showTrailingColumns = columnsData.length > MAX_COLUMNS; const extraColumns = columnsData.length - MAX_COLUMNS; @@ -300,7 +300,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, se iconType="help" iconSide="left" color="primary" - href={`${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-classification`} + href={docLink} > {i18n.translate( 'xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink', diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx index 123c84d59db2d..91d467a98ae35 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx @@ -61,7 +61,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) const { services: { docLinks }, } = useMlKibana(); - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; + const docLink = docLinks.links.ml.regressionEvaluation; const [trainingEval, setTrainingEval] = useState(defaultEval); const [generalizationEval, setGeneralizationEval] = useState(defaultEval); const [isLoadingTraining, setIsLoadingTraining] = useState(false); @@ -236,7 +236,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) iconType="help" iconSide="left" color="primary" - href={`${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-regression-evaluation`} + href={docLink} > {i18n.translate( 'xpack.ml.dataframe.analytics.regressionExploration.regressionDocsLink', diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/total_feature_importance_summary/feature_importance_summary.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/total_feature_importance_summary/feature_importance_summary.tsx index 96b2cc7da2309..53802098424e5 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/total_feature_importance_summary/feature_importance_summary.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/total_feature_importance_summary/feature_importance_summary.tsx @@ -191,7 +191,7 @@ export const FeatureImportanceSummaryPanel: FC Number(d.toPrecision(3)).toString(), []); // do not expand by default if no feature importance data @@ -256,7 +256,7 @@ export const FeatureImportanceSummaryPanel: FC { const { services: { docLinks }, } = useMlKibana(); - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; - const docsUrl = `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-calendars.html`; + const docsUrl = docLinks.links.ml.calendars; const title = i18n.translate( 'xpack.ml.newJob.wizard.jobDetailsStep.additionalSection.calendarsSelection.title', { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx index 40974418b09b1..c1b595d6b2579 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx @@ -14,8 +14,7 @@ export const Description: FC = memo(({ children }) => { const { services: { docLinks }, } = useMlKibana(); - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; - const docsUrl = `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-configuring-url.html`; + const docsUrl = docLinks.links.ml.customUrls; const title = i18n.translate( 'xpack.ml.newJob.wizard.jobDetailsStep.additionalSection.customUrls.title', { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/description.tsx index a09b6540e101f..f50308f060c89 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/description.tsx @@ -22,8 +22,7 @@ export const Description: FC = memo(({ children, validation }) => { const { services: { docLinks }, } = useMlKibana(); - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; - const docsUrl = `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-configuring-aggregation.html`; + const docsUrl = docLinks.links.ml.aggregations; return ( {title}} From 9c74a1090ec5320ffeb4cda0cecf6f25fbc6ef0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Tue, 22 Dec 2020 17:33:36 +0100 Subject: [PATCH 08/72] [Security Solution] Fix artifacts in Events table (#86767) --- .../data_driven_columns/__snapshots__/index.test.tsx.snap | 7 ------- .../timeline/body/data_driven_columns/index.tsx | 8 ++++---- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap index d112a665d77c0..8f514ca49e848 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap @@ -44,7 +44,6 @@ exports[`Columns it renders the expected columns 1`] = ` truncate={true} /> - 0 - 0 - 0 - 0 - 0 - 0 - 0 `; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx index c497d4f459f00..0242a2a0ff091 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx @@ -117,17 +117,17 @@ export const DataDrivenColumns = React.memo( })} - {hasRowRenderers && ( + {hasRowRenderers ? (

    {i18n.EVENT_HAS_AN_EVENT_RENDERER(ariaRowindex)}

    - )} + ) : null} - {notesCount && ( + {notesCount ? (

    {i18n.EVENT_HAS_NOTES({ row: ariaRowindex, notesCount })}

    - )} + ) : null} ))} From 57a72a78f7ce7054d148ccf5e95f6852b376e5f1 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 22 Dec 2020 17:35:27 +0100 Subject: [PATCH 09/72] [Lens] Configurable color syncing (#86180) --- ...ugins-embeddable-public.embeddableinput.md | 1 + ...c.expressionrenderhandler._constructor_.md | 4 +- ...ressions-public.expressionrenderhandler.md | 2 +- ...ressions-public.iexpressionloaderparams.md | 1 + ...blic.iexpressionloaderparams.synccolors.md | 11 + ...reterrenderhandlers.issynccolorsenabled.md | 11 + ...sions-public.iinterpreterrenderhandlers.md | 1 + ...reterrenderhandlers.issynccolorsenabled.md | 11 + ...sions-server.iinterpreterrenderhandlers.md | 1 + docs/user/dashboard/edit-dashboards.asciidoc | 15 + .../services/mapped_colors/mapped_colors.ts | 6 +- .../services/palettes/palettes.test.tsx | 410 +++++++++++--- .../public/services/palettes/palettes.tsx | 39 +- .../charts/public/services/palettes/types.ts | 5 + .../application/dashboard_app_functions.ts | 1 + .../application/dashboard_state.test.ts | 1 + .../application/dashboard_state_manager.ts | 9 + .../embeddable/dashboard_container.tsx | 4 + .../dashboard_container_factory.tsx | 1 + .../application/top_nav/dashboard_top_nav.tsx | 4 + .../public/application/top_nav/options.tsx | 21 + .../top_nav/show_options_popover.tsx | 6 + src/plugins/dashboard/public/types.ts | 1 + src/plugins/embeddable/common/types.ts | 5 + src/plugins/embeddable/public/public.api.md | 1 + .../common/expression_renderers/types.ts | 1 + src/plugins/expressions/public/loader.ts | 1 + src/plugins/expressions/public/public.api.md | 6 +- .../public/react_expression_renderer.tsx | 7 +- src/plugins/expressions/public/render.ts | 5 + src/plugins/expressions/public/types/index.ts | 1 + src/plugins/expressions/server/server.api.md | 2 + .../renderers/__stories__/render.tsx | 1 + .../canvas/public/lib/create_handlers.ts | 3 + .../embeddable/embeddable.tsx | 1 + .../embeddable/expression_wrapper.tsx | 3 + .../public/pie_visualization/expression.tsx | 1 + .../render_function.test.tsx | 2 + .../pie_visualization/render_function.tsx | 4 +- .../xy_visualization/expression.test.tsx | 518 +++--------------- .../public/xy_visualization/expression.tsx | 6 +- 41 files changed, 574 insertions(+), 560 deletions(-) create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.synccolors.md create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.issynccolorsenabled.md create mode 100644 docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.issynccolorsenabled.md diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md index 0f14215ff1309..07ede291e33d2 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md @@ -17,5 +17,6 @@ export declare type EmbeddableInput = { disabledActions?: string[]; disableTriggers?: boolean; searchSessionId?: string; + syncColors?: boolean; }; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md index 1565202e84674..9dfad91c33679 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `ExpressionRenderHandler` class Signature: ```typescript -constructor(element: HTMLElement, { onRenderError, renderMode, hasCompatibleActions, }?: ExpressionRenderHandlerParams); +constructor(element: HTMLElement, { onRenderError, renderMode, syncColors, hasCompatibleActions, }?: ExpressionRenderHandlerParams); ``` ## Parameters @@ -17,5 +17,5 @@ constructor(element: HTMLElement, { onRenderError, renderMode, hasCompatibleActi | Parameter | Type | Description | | --- | --- | --- | | element | HTMLElement | | -| { onRenderError, renderMode, hasCompatibleActions, } | ExpressionRenderHandlerParams | | +| { onRenderError, renderMode, syncColors, hasCompatibleActions, } | ExpressionRenderHandlerParams | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md index d65c06bdaed83..1a7050f3ffd4e 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md @@ -14,7 +14,7 @@ export declare class ExpressionRenderHandler | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(element, { onRenderError, renderMode, hasCompatibleActions, })](./kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md) | | Constructs a new instance of the ExpressionRenderHandler class | +| [(constructor)(element, { onRenderError, renderMode, syncColors, hasCompatibleActions, })](./kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md) | | Constructs a new instance of the ExpressionRenderHandler class | ## Properties diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md index 22a73fff039e6..4ef1225ae0d7e 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md @@ -25,6 +25,7 @@ export interface IExpressionLoaderParams | [renderMode](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.rendermode.md) | RenderMode | | | [searchContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md) | SerializableState | | | [searchSessionId](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchsessionid.md) | string | | +| [syncColors](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.synccolors.md) | boolean | | | [uiState](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.uistate.md) | unknown | | | [variables](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.variables.md) | Record<string, any> | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.synccolors.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.synccolors.md new file mode 100644 index 0000000000000..619f54ad88ef2 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.synccolors.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [IExpressionLoaderParams](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) > [syncColors](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.synccolors.md) + +## IExpressionLoaderParams.syncColors property + +Signature: + +```typescript +syncColors?: boolean; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.issynccolorsenabled.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.issynccolorsenabled.md new file mode 100644 index 0000000000000..6cdc796bf464b --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.issynccolorsenabled.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [IInterpreterRenderHandlers](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md) > [isSyncColorsEnabled](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.issynccolorsenabled.md) + +## IInterpreterRenderHandlers.isSyncColorsEnabled property + +Signature: + +```typescript +isSyncColorsEnabled: () => boolean; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md index c22c8bc6b6245..0b39a9b4b3ea2 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md @@ -18,6 +18,7 @@ export interface IInterpreterRenderHandlers | [event](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.event.md) | (event: any) => void | | | [getRenderMode](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.getrendermode.md) | () => RenderMode | | | [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md) | (event: any) => Promise<boolean> | | +| [isSyncColorsEnabled](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.issynccolorsenabled.md) | () => boolean | | | [onDestroy](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.ondestroy.md) | (fn: () => void) => void | | | [reload](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.reload.md) | () => void | | | [uiState](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md) | unknown | This uiState interface is actually PersistedState from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.issynccolorsenabled.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.issynccolorsenabled.md new file mode 100644 index 0000000000000..71a7e020e65a5 --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.issynccolorsenabled.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [IInterpreterRenderHandlers](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md) > [isSyncColorsEnabled](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.issynccolorsenabled.md) + +## IInterpreterRenderHandlers.isSyncColorsEnabled property + +Signature: + +```typescript +isSyncColorsEnabled: () => boolean; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md index 547608f40e6aa..831c9023c7e48 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md @@ -18,6 +18,7 @@ export interface IInterpreterRenderHandlers | [event](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.event.md) | (event: any) => void | | | [getRenderMode](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.getrendermode.md) | () => RenderMode | | | [hasCompatibleActions](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md) | (event: any) => Promise<boolean> | | +| [isSyncColorsEnabled](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.issynccolorsenabled.md) | () => boolean | | | [onDestroy](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.ondestroy.md) | (fn: () => void) => void | | | [reload](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.reload.md) | () => void | | | [uiState](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md) | unknown | This uiState interface is actually PersistedState from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. | diff --git a/docs/user/dashboard/edit-dashboards.asciidoc b/docs/user/dashboard/edit-dashboards.asciidoc index 7b712b355b315..d7f7dc2d65c85 100644 --- a/docs/user/dashboard/edit-dashboards.asciidoc +++ b/docs/user/dashboard/edit-dashboards.asciidoc @@ -81,6 +81,21 @@ Put the dashboard in *Edit* mode, then use the following options: * To delete, open the panel menu, then select *Delete from dashboard*. When you delete a panel from the dashboard, the visualization or saved search from the panel is still available in Kibana. +[float] +[[sync-colors]] +=== Synchronize colors + +By default, dashboard panels that share a non-gradient based color palette will synchronize their color assignment to improve readability. +Color assignment is based on the series name, and the total number of colors is based on the number of unique series names. + +The color synchronizing logic can make the dashboard less readable when there are too many unique series names. It is possible to disable the synchronization behavior: + +. Put the dashboard in *Edit* mode. + +. Click the "Options" button in the top navigation bar. + +. Disable "Sync color palettes across panels". + [float] [[clone-panels]] === Clone panels diff --git a/src/plugins/charts/public/services/mapped_colors/mapped_colors.ts b/src/plugins/charts/public/services/mapped_colors/mapped_colors.ts index 2934d4208d22c..7848cdd3f3140 100644 --- a/src/plugins/charts/public/services/mapped_colors/mapped_colors.ts +++ b/src/plugins/charts/public/services/mapped_colors/mapped_colors.ts @@ -37,15 +37,15 @@ export class MappedColors { private _mapping: any; constructor( - private uiSettings: CoreSetup['uiSettings'], + private uiSettings?: CoreSetup['uiSettings'], private colorPaletteFn: (num: number) => string[] = createColorPalette ) { this._oldMap = {}; this._mapping = {}; } - private getConfigColorMapping() { - return _.mapValues(this.uiSettings.get(COLOR_MAPPING_SETTING), standardizeColor); + private getConfigColorMapping(): Record { + return _.mapValues(this.uiSettings?.get(COLOR_MAPPING_SETTING) || {}, standardizeColor); } public get oldMap(): any { diff --git a/src/plugins/charts/public/services/palettes/palettes.test.tsx b/src/plugins/charts/public/services/palettes/palettes.test.tsx index 5d9337f1ee683..7356f13fddf9f 100644 --- a/src/plugins/charts/public/services/palettes/palettes.test.tsx +++ b/src/plugins/charts/public/services/palettes/palettes.test.tsx @@ -18,9 +18,11 @@ */ import { coreMock } from '../../../../../core/public/mocks'; +import { createColorPalette as createLegacyColorPalette } from '../../../../../../src/plugins/charts/public'; import { PaletteDefinition } from './types'; import { buildPalettes } from './palettes'; import { colorsServiceMock } from '../legacy_colors/mock'; +import { euiPaletteColorBlind, euiPaletteColorBlindBehindText } from '@elastic/eui'; describe('palettes', () => { const palettes: Record = buildPalettes( @@ -28,79 +30,257 @@ describe('palettes', () => { colorsServiceMock ); describe('default palette', () => { - it('should return different colors based on behind text flag', () => { - const palette = palettes.default; + describe('syncColors: false', () => { + it('should return different colors based on behind text flag', () => { + const palette = palettes.default; - const color1 = palette.getColor([ - { - name: 'abc', - rankAtDepth: 0, - totalSeriesAtDepth: 5, - }, - ]); - const color2 = palette.getColor( - [ + const color1 = palette.getColor([ { name: 'abc', rankAtDepth: 0, totalSeriesAtDepth: 5, }, - ], - { - behindText: true, - } - ); - expect(color1).not.toEqual(color2); - }); + ]); + const color2 = palette.getColor( + [ + { + name: 'abc', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + ], + { + behindText: true, + } + ); + expect(color1).not.toEqual(color2); + }); - it('should return different colors based on rank at current series', () => { - const palette = palettes.default; + it('should return different colors based on rank at current series', () => { + const palette = palettes.default; - const color1 = palette.getColor([ - { - name: 'abc', - rankAtDepth: 0, - totalSeriesAtDepth: 5, - }, - ]); - const color2 = palette.getColor([ - { - name: 'abc', - rankAtDepth: 1, - totalSeriesAtDepth: 5, - }, - ]); - expect(color1).not.toEqual(color2); + const color1 = palette.getColor([ + { + name: 'abc', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + ]); + const color2 = palette.getColor([ + { + name: 'abc', + rankAtDepth: 1, + totalSeriesAtDepth: 5, + }, + ]); + expect(color1).not.toEqual(color2); + }); + + it('should return the same color for different positions on outer series layers', () => { + const palette = palettes.default; + + const color1 = palette.getColor([ + { + name: 'abc', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + { + name: 'def', + rankAtDepth: 0, + totalSeriesAtDepth: 2, + }, + ]); + const color2 = palette.getColor([ + { + name: 'abc', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + { + name: 'ghj', + rankAtDepth: 1, + totalSeriesAtDepth: 1, + }, + ]); + expect(color1).toEqual(color2); + }); }); - it('should return the same color for different positions on outer series layers', () => { - const palette = palettes.default; + describe('syncColors: true', () => { + it('should return different colors based on behind text flag', () => { + const palette = palettes.default; - const color1 = palette.getColor([ - { - name: 'abc', - rankAtDepth: 0, - totalSeriesAtDepth: 5, - }, - { - name: 'def', - rankAtDepth: 0, - totalSeriesAtDepth: 2, - }, - ]); - const color2 = palette.getColor([ - { - name: 'abc', - rankAtDepth: 0, - totalSeriesAtDepth: 5, - }, - { - name: 'ghj', - rankAtDepth: 1, - totalSeriesAtDepth: 1, - }, - ]); - expect(color1).toEqual(color2); + const color1 = palette.getColor( + [ + { + name: 'abc', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + ], + { + syncColors: true, + } + ); + const color2 = palette.getColor( + [ + { + name: 'abc', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + ], + { + behindText: true, + syncColors: true, + } + ); + expect(color1).not.toEqual(color2); + }); + + it('should return different colors for different keys', () => { + const palette = palettes.default; + + const color1 = palette.getColor( + [ + { + name: 'abc', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + ], + { + syncColors: true, + } + ); + const color2 = palette.getColor( + [ + { + name: 'def', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + ], + { + syncColors: true, + } + ); + expect(color1).not.toEqual(color2); + }); + + it('should return the same color for the same key, irregardless of rank', () => { + const palette = palettes.default; + + const color1 = palette.getColor( + [ + { + name: 'hij', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + ], + { + syncColors: true, + } + ); + const color2 = palette.getColor( + [ + { + name: 'hij', + rankAtDepth: 5, + totalSeriesAtDepth: 5, + }, + ], + { + syncColors: true, + } + ); + expect(color1).toEqual(color2); + }); + + it('should return the same color for different positions on outer series layers', () => { + const palette = palettes.default; + + const color1 = palette.getColor( + [ + { + name: 'klm', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + { + name: 'def', + rankAtDepth: 0, + totalSeriesAtDepth: 2, + }, + ], + { + syncColors: true, + } + ); + const color2 = palette.getColor( + [ + { + name: 'klm', + rankAtDepth: 3, + totalSeriesAtDepth: 5, + }, + { + name: 'ghj', + rankAtDepth: 1, + totalSeriesAtDepth: 1, + }, + ], + { + syncColors: true, + } + ); + expect(color1).toEqual(color2); + }); + + it('should return the same index of the behind text palette for same key', () => { + const palette = palettes.default; + + const color1 = palette.getColor( + [ + { + name: 'klm', + rankAtDepth: 0, + totalSeriesAtDepth: 5, + }, + { + name: 'def', + rankAtDepth: 0, + totalSeriesAtDepth: 2, + }, + ], + { + syncColors: true, + } + ); + const color2 = palette.getColor( + [ + { + name: 'klm', + rankAtDepth: 3, + totalSeriesAtDepth: 5, + }, + { + name: 'ghj', + rankAtDepth: 1, + totalSeriesAtDepth: 1, + }, + ], + { + syncColors: true, + behindText: true, + } + ); + const color1Index = euiPaletteColorBlind({ rotations: 2 }).indexOf(color1!); + const color2Index = euiPaletteColorBlindBehindText({ rotations: 2 }).indexOf(color2!); + expect(color1Index).toEqual(color2Index); + }); }); }); @@ -136,35 +316,87 @@ describe('palettes', () => { (colorsServiceMock.mappedColors.get as jest.Mock).mockClear(); }); - it('should query legacy color service', () => { - palette.getColor([ - { - name: 'abc', - rankAtDepth: 0, - totalSeriesAtDepth: 10, - }, - ]); - expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledWith(['abc']); - expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc'); + describe('syncColors: false', () => { + it('should not query legacy color service', () => { + palette.getColor( + [ + { + name: 'abc', + rankAtDepth: 0, + totalSeriesAtDepth: 10, + }, + ], + { + syncColors: false, + } + ); + expect(colorsServiceMock.mappedColors.mapKeys).not.toHaveBeenCalled(); + expect(colorsServiceMock.mappedColors.get).not.toHaveBeenCalled(); + }); + + it('should return a color from the legacy palette based on position of first series', () => { + const result = palette.getColor( + [ + { + name: 'abc', + rankAtDepth: 2, + totalSeriesAtDepth: 10, + }, + { + name: 'def', + rankAtDepth: 0, + totalSeriesAtDepth: 10, + }, + ], + { + syncColors: false, + } + ); + expect(result).toEqual(createLegacyColorPalette(20)[2]); + }); }); - it('should always use root series', () => { - palette.getColor([ - { - name: 'abc', - rankAtDepth: 0, - totalSeriesAtDepth: 10, - }, - { - name: 'def', - rankAtDepth: 0, - totalSeriesAtDepth: 10, - }, - ]); - expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledTimes(1); - expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledWith(['abc']); - expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledTimes(1); - expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc'); + describe('syncColors: true', () => { + it('should query legacy color service', () => { + palette.getColor( + [ + { + name: 'abc', + rankAtDepth: 0, + totalSeriesAtDepth: 10, + }, + ], + { + syncColors: true, + } + ); + expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledWith(['abc']); + expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc'); + }); + + it('should always use root series', () => { + palette.getColor( + [ + { + name: 'abc', + rankAtDepth: 0, + totalSeriesAtDepth: 10, + }, + { + name: 'def', + rankAtDepth: 0, + totalSeriesAtDepth: 10, + }, + ], + { + syncColors: true, + } + ); + expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledTimes(1); + expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledWith(['abc']); + expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledTimes(1); + expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc'); + }); }); }); diff --git a/src/plugins/charts/public/services/palettes/palettes.tsx b/src/plugins/charts/public/services/palettes/palettes.tsx index c1fd7c3cc739f..ffb237904b36c 100644 --- a/src/plugins/charts/public/services/palettes/palettes.tsx +++ b/src/plugins/charts/public/services/palettes/palettes.tsx @@ -28,26 +28,45 @@ import { euiPaletteNegative, euiPalettePositive, euiPaletteWarm, - euiPaletteColorBlindBehindText, euiPaletteForStatus, euiPaletteForTemperature, euiPaletteComplimentary, + euiPaletteColorBlindBehindText, } from '@elastic/eui'; -import { ChartsPluginSetup } from '../../../../../../src/plugins/charts/public'; +import { flatten, zip } from 'lodash'; +import { + ChartsPluginSetup, + createColorPalette as createLegacyColorPalette, +} from '../../../../../../src/plugins/charts/public'; import { lightenColor } from './lighten_color'; import { ChartColorConfiguration, PaletteDefinition, SeriesLayer } from './types'; import { LegacyColorsService } from '../legacy_colors'; +import { MappedColors } from '../mapped_colors'; function buildRoundRobinCategoricalWithMappedColors(): Omit { const colors = euiPaletteColorBlind({ rotations: 2 }); const behindTextColors = euiPaletteColorBlindBehindText({ rotations: 2 }); + const behindTextColorMap: Record = Object.fromEntries( + zip(colors, behindTextColors) + ); + const mappedColors = new MappedColors(undefined, (num: number) => { + return flatten(new Array(Math.ceil(num / 10)).fill(colors)).map((color) => color.toLowerCase()); + }); function getColor( series: SeriesLayer[], chartConfiguration: ChartColorConfiguration = { behindText: false } ) { - const outputColor = chartConfiguration.behindText - ? behindTextColors[series[0].rankAtDepth % behindTextColors.length] - : colors[series[0].rankAtDepth % colors.length]; + let outputColor: string; + if (chartConfiguration.syncColors) { + const colorKey = series[0].name; + mappedColors.mapKeys([colorKey]); + const mappedColor = mappedColors.get(colorKey); + outputColor = chartConfiguration.behindText ? behindTextColorMap[mappedColor] : mappedColor; + } else { + outputColor = chartConfiguration.behindText + ? behindTextColors[series[0].rankAtDepth % behindTextColors.length] + : colors[series[0].rankAtDepth % colors.length]; + } if (!chartConfiguration.maxDepth || chartConfiguration.maxDepth === 1) { return outputColor; @@ -115,9 +134,15 @@ function buildGradient( function buildSyncedKibanaPalette( colors: ChartsPluginSetup['legacyColors'] ): Omit { + const staticColors = createLegacyColorPalette(20); function getColor(series: SeriesLayer[], chartConfiguration: ChartColorConfiguration = {}) { - colors.mappedColors.mapKeys([series[0].name]); - const outputColor = colors.mappedColors.get(series[0].name); + let outputColor: string; + if (chartConfiguration.syncColors) { + colors.mappedColors.mapKeys([series[0].name]); + outputColor = colors.mappedColors.get(series[0].name); + } else { + outputColor = staticColors[series[0].rankAtDepth % staticColors.length]; + } if (!chartConfiguration.maxDepth || chartConfiguration.maxDepth === 1) { return outputColor; diff --git a/src/plugins/charts/public/services/palettes/types.ts b/src/plugins/charts/public/services/palettes/types.ts index f92bcb4bd0824..15989578518f5 100644 --- a/src/plugins/charts/public/services/palettes/types.ts +++ b/src/plugins/charts/public/services/palettes/types.ts @@ -55,6 +55,11 @@ export interface ChartColorConfiguration { * adjust colors for better a11y. Might be ignored depending on the palette. */ behindText?: boolean; + /** + * Flag whether a color assignment to a given key should be remembered and re-used the next time the key shows up. + * This setting might be ignored based on the palette. + */ + syncColors?: boolean; } /** diff --git a/src/plugins/dashboard/public/application/dashboard_app_functions.ts b/src/plugins/dashboard/public/application/dashboard_app_functions.ts index 0381fdb2e55b5..af7a485296ea0 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_functions.ts +++ b/src/plugins/dashboard/public/application/dashboard_app_functions.ts @@ -151,6 +151,7 @@ export const getDashboardContainerInput = ({ description: dashboardStateManager.getDescription(), id: dashboardStateManager.savedDashboard.id || '', useMargins: dashboardStateManager.getUseMargins(), + syncColors: dashboardStateManager.getSyncColors(), viewMode: dashboardStateManager.getViewMode(), filters: query.filterManager.getFilters(), query: dashboardStateManager.getQuery(), diff --git a/src/plugins/dashboard/public/application/dashboard_state.test.ts b/src/plugins/dashboard/public/application/dashboard_state.test.ts index b07ea762f35e0..f31ed30f8eb80 100644 --- a/src/plugins/dashboard/public/application/dashboard_state.test.ts +++ b/src/plugins/dashboard/public/application/dashboard_state.test.ts @@ -68,6 +68,7 @@ describe('DashboardState', function () { query: {} as DashboardContainerInput['query'], timeRange: {} as DashboardContainerInput['timeRange'], useMargins: true, + syncColors: false, title: 'ultra awesome test dashboard', isFullScreenMode: false, panels: {} as DashboardContainerInput['panels'], diff --git a/src/plugins/dashboard/public/application/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts index daa0bbdfc9f8a..dfcbfcafd3db1 100644 --- a/src/plugins/dashboard/public/application/dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/dashboard_state_manager.ts @@ -404,6 +404,15 @@ export class DashboardStateManager { this.stateContainer.transitions.setOption('useMargins', useMargins); } + public getSyncColors() { + // Existing dashboards that don't define this should default to true. + return this.appState.options.syncColors === undefined ? true : this.appState.options.syncColors; + } + + public setSyncColors(syncColors: boolean) { + this.stateContainer.transitions.setOption('syncColors', syncColors); + } + public getHidePanelTitles() { return this.appState.options.hidePanelTitles; } diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 01b4e81fc484c..a3b67ede9f3f9 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -59,6 +59,7 @@ export interface DashboardContainerInput extends ContainerInput { timeRange: TimeRange; description?: string; useMargins: boolean; + syncColors?: boolean; viewMode: ViewMode; filters: Filter[]; title: string; @@ -93,6 +94,7 @@ export interface InheritedChildInput extends IndexSignature { hidePanelTitles?: boolean; id: string; searchSessionId?: string; + syncColors?: boolean; } export type DashboardReactContextValue = KibanaReactContextValue; @@ -269,6 +271,7 @@ export class DashboardContainer extends Container { dashboardStateManager.setUseMargins(isChecked); }, + syncColors: dashboardStateManager.getSyncColors(), + onSyncColorsChange: (isChecked: boolean) => { + dashboardStateManager.setSyncColors(isChecked); + }, hidePanelTitles: dashboardStateManager.getHidePanelTitles(), onHidePanelTitlesChange: (isChecked: boolean) => { dashboardStateManager.setHidePanelTitles(isChecked); diff --git a/src/plugins/dashboard/public/application/top_nav/options.tsx b/src/plugins/dashboard/public/application/top_nav/options.tsx index 3398696ff40db..86409cdeba74f 100644 --- a/src/plugins/dashboard/public/application/top_nav/options.tsx +++ b/src/plugins/dashboard/public/application/top_nav/options.tsx @@ -27,17 +27,21 @@ interface Props { onUseMarginsChange: (useMargins: boolean) => void; hidePanelTitles: boolean; onHidePanelTitlesChange: (hideTitles: boolean) => void; + syncColors: boolean; + onSyncColorsChange: (syncColors: boolean) => void; } interface State { useMargins: boolean; hidePanelTitles: boolean; + syncColors: boolean; } export class OptionsMenu extends Component { state = { useMargins: this.props.useMargins, hidePanelTitles: this.props.hidePanelTitles, + syncColors: this.props.syncColors, }; constructor(props: Props) { @@ -56,6 +60,12 @@ export class OptionsMenu extends Component { this.setState({ hidePanelTitles: isChecked }); }; + handleSyncColorsChange = (evt: any) => { + const isChecked = evt.target.checked; + this.props.onSyncColorsChange(isChecked); + this.setState({ syncColors: isChecked }); + }; + render() { return ( @@ -80,6 +90,17 @@ export class OptionsMenu extends Component { data-test-subj="dashboardPanelTitlesCheckbox" /> + + + + ); } diff --git a/src/plugins/dashboard/public/application/top_nav/show_options_popover.tsx b/src/plugins/dashboard/public/application/top_nav/show_options_popover.tsx index 7c23e4808fbea..6c519ccad327f 100644 --- a/src/plugins/dashboard/public/application/top_nav/show_options_popover.tsx +++ b/src/plugins/dashboard/public/application/top_nav/show_options_popover.tsx @@ -39,10 +39,14 @@ export function showOptionsPopover({ onUseMarginsChange, hidePanelTitles, onHidePanelTitlesChange, + syncColors, + onSyncColorsChange, }: { anchorElement: HTMLElement; useMargins: boolean; onUseMarginsChange: (useMargins: boolean) => void; + syncColors: boolean; + onSyncColorsChange: (syncColors: boolean) => void; hidePanelTitles: boolean; onHidePanelTitlesChange: (hideTitles: boolean) => void; }) { @@ -62,6 +66,8 @@ export function showOptionsPopover({ onUseMarginsChange={onUseMarginsChange} hidePanelTitles={hidePanelTitles} onHidePanelTitlesChange={onHidePanelTitlesChange} + syncColors={syncColors} + onSyncColorsChange={onSyncColorsChange} /> diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts index 7e859a81d9d4d..882c5b4286263 100644 --- a/src/plugins/dashboard/public/types.ts +++ b/src/plugins/dashboard/public/types.ts @@ -78,6 +78,7 @@ export interface DashboardAppState { options: { hidePanelTitles: boolean; useMargins: boolean; + syncColors?: boolean; }; query: Query | string; filters: Filter[]; diff --git a/src/plugins/embeddable/common/types.ts b/src/plugins/embeddable/common/types.ts index d893724f616d2..8366d81a65754 100644 --- a/src/plugins/embeddable/common/types.ts +++ b/src/plugins/embeddable/common/types.ts @@ -55,6 +55,11 @@ export type EmbeddableInput = { * Search session id to group searches */ searchSessionId?: string; + + /** + * Flag whether colors should be synced with other panels + */ + syncColors?: boolean; }; export interface PanelState { diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index a401795c498b3..ab039d15a2ce6 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -410,6 +410,7 @@ export type EmbeddableInput = { disabledActions?: string[]; disableTriggers?: boolean; searchSessionId?: string; + syncColors?: boolean; }; // Warning: (ae-missing-release-tag) "EmbeddableInstanceConfiguration" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/expressions/common/expression_renderers/types.ts b/src/plugins/expressions/common/expression_renderers/types.ts index fca1694747ce2..3f3cfb9ed2dd9 100644 --- a/src/plugins/expressions/common/expression_renderers/types.ts +++ b/src/plugins/expressions/common/expression_renderers/types.ts @@ -82,6 +82,7 @@ export interface IInterpreterRenderHandlers { event: (event: any) => void; hasCompatibleActions?: (event: any) => Promise; getRenderMode: () => RenderMode; + isSyncColorsEnabled: () => boolean; /** * This uiState interface is actually `PersistedState` from the visualizations plugin, * but expressions cannot know about vis or it creates a mess of circular dependencies. diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index e9e0fa18af6c3..1cf499ce2635a 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -64,6 +64,7 @@ export class ExpressionLoader { this.renderHandler = new ExpressionRenderHandler(element, { onRenderError: params && params.onRenderError, renderMode: params?.renderMode, + syncColors: params?.syncColors, hasCompatibleActions: params?.hasCompatibleActions, }); this.render$ = this.renderHandler.render$; diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 404df2db019a1..5c018adc0131b 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -531,7 +531,7 @@ export interface ExpressionRenderError extends Error { // @public (undocumented) export class ExpressionRenderHandler { // Warning: (ae-forgotten-export) The symbol "ExpressionRenderHandlerParams" needs to be exported by the entry point index.d.ts - constructor(element: HTMLElement, { onRenderError, renderMode, hasCompatibleActions, }?: ExpressionRenderHandlerParams); + constructor(element: HTMLElement, { onRenderError, renderMode, syncColors, hasCompatibleActions, }?: ExpressionRenderHandlerParams); // (undocumented) destroy: () => void; // (undocumented) @@ -903,6 +903,8 @@ export interface IExpressionLoaderParams { // (undocumented) searchSessionId?: string; // (undocumented) + syncColors?: boolean; + // (undocumented) uiState?: unknown; // (undocumented) variables?: Record; @@ -920,6 +922,8 @@ export interface IInterpreterRenderHandlers { // (undocumented) hasCompatibleActions?: (event: any) => Promise; // (undocumented) + isSyncColorsEnabled: () => boolean; + // (undocumented) onDestroy: (fn: () => void) => void; // (undocumented) reload: () => void; diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index eac2371ec66d0..d19f482107845 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -170,7 +170,12 @@ export const ReactExpressionRenderer = ({ errorRenderHandlerRef.current = null; }; - }, [hasCustomRenderErrorHandler, onEvent]); + }, [ + hasCustomRenderErrorHandler, + onEvent, + expressionLoaderOptions.renderMode, + expressionLoaderOptions.syncColors, + ]); useEffect(() => { const subscription = reload$?.subscribe(() => { diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts index 717776a2861b4..e3091b908deca 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -31,6 +31,7 @@ export type IExpressionRendererExtraHandlers = Record; export interface ExpressionRenderHandlerParams { onRenderError?: RenderErrorHandlerFnType; renderMode?: RenderMode; + syncColors?: boolean; hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise; } @@ -63,6 +64,7 @@ export class ExpressionRenderHandler { { onRenderError, renderMode, + syncColors, hasCompatibleActions = async () => false, }: ExpressionRenderHandlerParams = {} ) { @@ -101,6 +103,9 @@ export class ExpressionRenderHandler { getRenderMode: () => { return renderMode || 'display'; }, + isSyncColorsEnabled: () => { + return syncColors || false; + }, hasCompatibleActions, }; } diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index f37107abbb716..d709d8ca96bbd 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -57,6 +57,7 @@ export interface IExpressionLoaderParams { onRenderError?: RenderErrorHandlerFnType; searchSessionId?: string; renderMode?: RenderMode; + syncColors?: boolean; hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions']; } diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index 8b8678371dd83..71199560ee0c7 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -737,6 +737,8 @@ export interface IInterpreterRenderHandlers { // (undocumented) hasCompatibleActions?: (event: any) => Promise; // (undocumented) + isSyncColorsEnabled: () => boolean; + // (undocumented) onDestroy: (fn: () => void) => void; // (undocumented) reload: () => void; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/render.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/render.tsx index 54702f2654839..34f4bb39fbfa7 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/render.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/render.tsx @@ -12,6 +12,7 @@ export const defaultHandlers: RendererHandlers = { getElementId: () => 'element-id', getFilter: () => 'filter', getRenderMode: () => 'display', + isSyncColorsEnabled: () => false, onComplete: (fn) => undefined, onEmbeddableDestroyed: action('onEmbeddableDestroyed'), onEmbeddableInputChange: action('onEmbeddableInputChange'), diff --git a/x-pack/plugins/canvas/public/lib/create_handlers.ts b/x-pack/plugins/canvas/public/lib/create_handlers.ts index 9bc4bd5e78fd0..4c9dbd92d3f21 100644 --- a/x-pack/plugins/canvas/public/lib/create_handlers.ts +++ b/x-pack/plugins/canvas/public/lib/create_handlers.ts @@ -26,6 +26,9 @@ export const createHandlers = (): RendererHandlers => ({ getRenderMode() { return 'display'; }, + isSyncColorsEnabled() { + return false; + }, onComplete(fn: () => void) { this.done = fn; }, diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index b00760e9664f3..ea7ce99e92cef 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -260,6 +260,7 @@ export class Embeddable handleEvent={this.handleEvent} onData$={this.updateActiveData} renderMode={input.renderMode} + syncColors={input.syncColors} hasCompatibleActions={this.hasCompatibleActions} />, domNode diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx index e2607886a4219..c91ca74b54a4f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx @@ -29,6 +29,7 @@ export interface ExpressionWrapperProps { inspectorAdapters?: Partial | undefined ) => void; renderMode?: RenderMode; + syncColors?: boolean; hasCompatibleActions?: ReactExpressionRendererProps['hasCompatibleActions']; } @@ -41,6 +42,7 @@ export function ExpressionWrapper({ searchSessionId, onData$, renderMode, + syncColors, hasCompatibleActions, }: ExpressionWrapperProps) { return ( @@ -70,6 +72,7 @@ export function ExpressionWrapper({ searchSessionId={searchSessionId} onData$={onData$} renderMode={renderMode} + syncColors={syncColors} renderError={(errorMessage, error) => (
    diff --git a/x-pack/plugins/lens/public/pie_visualization/expression.tsx b/x-pack/plugins/lens/public/pie_visualization/expression.tsx index 5f18ef7c7f637..63261d08ff1a4 100644 --- a/x-pack/plugins/lens/public/pie_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/expression.tsx @@ -140,6 +140,7 @@ export const getPieRenderer = (dependencies: { paletteService={dependencies.paletteService} onClickValue={onClickValue} renderMode={handlers.getRenderMode()} + syncColors={handlers.isSyncColorsEnabled()} /> , domNode, diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx index 458b1a75c4c17..c6eed36f81ab0 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx @@ -71,6 +71,7 @@ describe('PieVisualization component', () => { chartsThemeService, paletteService: chartPluginMock.createPaletteRegistry(), renderMode: 'display' as const, + syncColors: false, }; } @@ -172,6 +173,7 @@ describe('PieVisualization component', () => { { maxDepth: 2, totalSeries: 5, + syncColors: false, behindText: true, }, undefined diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index 56ecf57f2dff7..70a98e4cf8589 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -47,12 +47,13 @@ export function PieComponent( paletteService: PaletteRegistry; onClickValue: (data: LensFilterEvent['data']) => void; renderMode: RenderMode; + syncColors: boolean; } ) { const [firstTable] = Object.values(props.data.tables); const formatters: Record> = {}; - const { chartsThemeService, paletteService, onClickValue } = props; + const { chartsThemeService, paletteService, syncColors, onClickValue } = props; const { shape, groups, @@ -145,6 +146,7 @@ export function PieComponent( behindText: categoryDisplay !== 'hide', maxDepth: bucketColumns.length, totalSeries: totalSeriesCount, + syncColors, }, palette.params ); diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 0e2b47410c3f9..97efd39c02fab 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -18,7 +18,13 @@ import { Fit, } from '@elastic/charts'; import { PaletteOutput } from 'src/plugins/charts/public'; -import { calculateMinInterval, xyChart, XYChart, XYChartProps } from './expression'; +import { + calculateMinInterval, + xyChart, + XYChart, + XYChartProps, + XYChartRenderProps, +} from './expression'; import { LensMultiTable } from '../types'; import { Datatable, DatatableRow } from '../../../../../src/plugins/expressions/public'; import React from 'react'; @@ -382,6 +388,7 @@ describe('xy_expression', () => { describe('XYChart component', () => { let getFormatSpy: jest.Mock; let convertSpy: jest.Mock; + let defaultProps: Omit; const dataWithoutFormats: LensMultiTable = { type: 'lens_multitable', @@ -421,26 +428,25 @@ describe('xy_expression', () => { }; const getRenderedComponent = (data: LensMultiTable, args: XYArgs) => { - return shallow( - - ); + return shallow(); }; beforeEach(() => { convertSpy = jest.fn((x) => x); getFormatSpy = jest.fn(); getFormatSpy.mockReturnValue({ convert: convertSpy }); + + defaultProps = { + formatFactory: getFormatSpy, + timeZone: 'UTC', + renderMode: 'display', + chartsThemeService, + paletteService, + minInterval: 50, + onClickValue, + onSelectRange, + syncColors: false, + }; }); test('it renders line', () => { @@ -448,16 +454,9 @@ describe('xy_expression', () => { const component = shallow( ); expect(component).toMatchSnapshot(); @@ -493,6 +492,7 @@ describe('xy_expression', () => { const component = shallow( { ...args, layers: [{ ...args.layers[0], seriesType: 'line', xScaleType: 'time' }], }} - formatFactory={getFormatSpy} - timeZone="UTC" - renderMode="display" - chartsThemeService={chartsThemeService} - paletteService={paletteService} minInterval={undefined} - onClickValue={onClickValue} - onSelectRange={onSelectRange} /> ); expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(` @@ -534,6 +527,7 @@ describe('xy_expression', () => { const component = shallow( { }, }} args={multiLayerArgs} - formatFactory={getFormatSpy} - timeZone="UTC" - renderMode="display" - chartsThemeService={chartsThemeService} - paletteService={paletteService} - minInterval={50} - onClickValue={onClickValue} - onSelectRange={onSelectRange} /> ); @@ -569,6 +555,7 @@ describe('xy_expression', () => { const component = shallow( { ...args, layers: [{ ...args.layers[0], seriesType: 'line', xScaleType: 'linear' }], }} - formatFactory={getFormatSpy} - timeZone="UTC" - renderMode="display" - chartsThemeService={chartsThemeService} - paletteService={paletteService} - minInterval={50} - onClickValue={onClickValue} - onSelectRange={onSelectRange} /> ); expect(component.find(Settings).prop('xDomain')).toBeUndefined(); @@ -597,16 +576,9 @@ describe('xy_expression', () => { const { data, args } = sampleArgs(); const component = shallow( ); expect(component).toMatchSnapshot(); @@ -619,16 +591,9 @@ describe('xy_expression', () => { const { data, args } = sampleArgs(); const component = shallow( ); expect(component).toMatchSnapshot(); @@ -641,16 +606,9 @@ describe('xy_expression', () => { const { data, args } = sampleArgs(); const component = shallow( ); expect(component).toMatchSnapshot(); @@ -666,20 +624,7 @@ describe('xy_expression', () => { // send empty data to the chart data.tables.first.rows = []; - const component = shallow( - - ); + const component = shallow(); expect(component.find(BarSeries)).toHaveLength(0); expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined(); @@ -690,19 +635,12 @@ describe('xy_expression', () => { const wrapper = mountWithIntl( ); @@ -776,19 +714,12 @@ describe('xy_expression', () => { const wrapper = mountWithIntl( ); @@ -806,18 +737,7 @@ describe('xy_expression', () => { const { args, data } = sampleArgs(); const wrapper = mountWithIntl( - + ); expect(wrapper.find(Settings).first().prop('onBrushEnd')).toBeUndefined(); @@ -837,6 +757,7 @@ describe('xy_expression', () => { const wrapper = mountWithIntl( { }, ], }} - formatFactory={getFormatSpy} - timeZone="UTC" - renderMode="display" - chartsThemeService={chartsThemeService} - paletteService={paletteService} - minInterval={50} - onClickValue={onClickValue} - onSelectRange={onSelectRange} /> ); @@ -892,18 +805,7 @@ describe('xy_expression', () => { const { args, data } = sampleArgs(); const wrapper = mountWithIntl( - + ); expect(wrapper.find(Settings).first().prop('onElementClick')).toBeUndefined(); @@ -913,16 +815,9 @@ describe('xy_expression', () => { const { data, args } = sampleArgs(); const component = shallow( ); expect(component).toMatchSnapshot(); @@ -935,16 +830,9 @@ describe('xy_expression', () => { const { data, args } = sampleArgs(); const component = shallow( ); expect(component).toMatchSnapshot(); @@ -957,19 +845,12 @@ describe('xy_expression', () => { const { data, args } = sampleArgs(); const component = shallow( ); expect(component).toMatchSnapshot(); @@ -984,6 +865,7 @@ describe('xy_expression', () => { const component = shallow( { }, ], }} - formatFactory={getFormatSpy} - timeZone="UTC" - renderMode="display" - chartsThemeService={chartsThemeService} - paletteService={paletteService} - minInterval={50} - onClickValue={onClickValue} - onSelectRange={onSelectRange} /> ); @@ -1014,18 +888,7 @@ describe('xy_expression', () => { test('it passes time zone to the series', () => { const { data, args } = sampleArgs(); const component = shallow( - + ); expect(component.find(LineSeries).at(0).prop('timeZone')).toEqual('CEST'); expect(component.find(LineSeries).at(1).prop('timeZone')).toEqual('CEST'); @@ -1041,18 +904,7 @@ describe('xy_expression', () => { }; delete firstLayer.splitAccessor; const component = shallow( - + ); expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(true); }); @@ -1062,18 +914,7 @@ describe('xy_expression', () => { const firstLayer: LayerArgs = { ...args.layers[0], seriesType: 'bar', isHistogram: true }; delete firstLayer.splitAccessor; const component = shallow( - + ); expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(false); expect(component.find(BarSeries).at(1).prop('enableHistogramMode')).toEqual(false); @@ -1087,16 +928,9 @@ describe('xy_expression', () => { delete secondLayer.splitAccessor; const component = shallow( ); expect(component.find(LineSeries).at(0).prop('enableHistogramMode')).toEqual(true); @@ -1107,6 +941,7 @@ describe('xy_expression', () => { const { data, args } = sampleArgs(); const component = shallow( { }, ], }} - formatFactory={getFormatSpy} - timeZone="UTC" - renderMode="display" - chartsThemeService={chartsThemeService} - paletteService={paletteService} - minInterval={50} - onClickValue={onClickValue} - onSelectRange={onSelectRange} /> ); expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(true); @@ -1136,19 +963,12 @@ describe('xy_expression', () => { const { data, args } = sampleArgs(); const component = shallow( ); expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(false); @@ -1541,16 +1361,9 @@ describe('xy_expression', () => { const component = shallow( ); expect(component.find(LineSeries).at(0).prop('xScaleType')).toEqual(ScaleType.Ordinal); @@ -1562,16 +1375,9 @@ describe('xy_expression', () => { const component = shallow( ); expect(component.find(LineSeries).at(0).prop('yScaleType')).toEqual(ScaleType.Sqrt); @@ -1581,20 +1387,7 @@ describe('xy_expression', () => { test('it gets the formatter for the x axis', () => { const { data, args } = sampleArgs(); - shallow( - - ); + shallow(); expect(getFormatSpy).toHaveBeenCalledWith({ id: 'string' }); }); @@ -1604,16 +1397,9 @@ describe('xy_expression', () => { shallow( ); expect(getFormatSpy).toHaveBeenCalledWith({ @@ -1625,20 +1411,7 @@ describe('xy_expression', () => { test('it should pass the formatter function to the axis', () => { const { data, args } = sampleArgs(); - const instance = shallow( - - ); + const instance = shallow(); const tickFormatter = instance.find(Axis).first().prop('tickFormat'); @@ -1661,20 +1434,7 @@ describe('xy_expression', () => { type: 'lens_xy_tickLabelsConfig', }; - const instance = shallow( - - ); + const instance = shallow(); const axisStyle = instance.find(Axis).first().prop('style'); @@ -1695,20 +1455,7 @@ describe('xy_expression', () => { type: 'lens_xy_tickLabelsConfig', }; - const instance = shallow( - - ); + const instance = shallow(); const axisStyle = instance.find(Axis).at(1).prop('style'); @@ -1729,20 +1476,7 @@ describe('xy_expression', () => { type: 'lens_xy_tickLabelsConfig', }; - const instance = shallow( - - ); + const instance = shallow(); const axisStyle = instance.find(Axis).first().prop('style'); @@ -1763,20 +1497,7 @@ describe('xy_expression', () => { type: 'lens_xy_tickLabelsConfig', }; - const instance = shallow( - - ); + const instance = shallow(); const axisStyle = instance.find(Axis).at(1).prop('style'); @@ -1864,20 +1585,7 @@ describe('xy_expression', () => { ], }; - const component = shallow( - - ); + const component = shallow(); const series = component.find(LineSeries); @@ -1939,20 +1647,7 @@ describe('xy_expression', () => { ], }; - const component = shallow( - - ); + const component = shallow(); const series = component.find(LineSeries); @@ -2012,20 +1707,7 @@ describe('xy_expression', () => { ], }; - const component = shallow( - - ); + const component = shallow(); expect(component.find(Settings).prop('showLegend')).toEqual(true); }); @@ -2035,20 +1717,13 @@ describe('xy_expression', () => { const component = shallow( ); @@ -2060,19 +1735,12 @@ describe('xy_expression', () => { const component = shallow( ); @@ -2084,19 +1752,12 @@ describe('xy_expression', () => { const component = shallow( ); @@ -2123,16 +1784,9 @@ describe('xy_expression', () => { const component = shallow( ); @@ -2150,18 +1804,7 @@ describe('xy_expression', () => { args.layers[0].accessors = ['a']; const component = shallow( - + ); expect(component.find(LineSeries).prop('fit')).toEqual({ type: Fit.None }); @@ -2173,18 +1816,7 @@ describe('xy_expression', () => { args.xTitle = 'My custom x-axis title'; const component = shallow( - + ); expect(component.find(Axis).at(0).prop('title')).toEqual('My custom x-axis title'); @@ -2201,18 +1833,7 @@ describe('xy_expression', () => { }; const component = shallow( - + ); const axisStyle = component.find(Axis).first().prop('style'); @@ -2235,18 +1856,7 @@ describe('xy_expression', () => { }; const component = shallow( - + ); expect(component.find(Axis).at(0).prop('gridLine')).toMatchObject({ diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 790416a6c920d..399ba705f2f5e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -76,7 +76,7 @@ export interface XYRender { value: XYChartProps; } -type XYChartRenderProps = XYChartProps & { +export type XYChartRenderProps = XYChartProps & { chartsThemeService: ChartsPluginSetup['theme']; paletteService: PaletteRegistry; formatFactory: FormatFactory; @@ -85,6 +85,7 @@ type XYChartRenderProps = XYChartProps & { onClickValue: (data: LensFilterEvent['data']) => void; onSelectRange: (data: LensBrushEvent['data']) => void; renderMode: RenderMode; + syncColors: boolean; }; export const xyChart: ExpressionFunctionDefinition< @@ -240,6 +241,7 @@ export const getXyChartRenderer = (dependencies: { onClickValue={onClickValue} onSelectRange={onSelectRange} renderMode={handlers.getRenderMode()} + syncColors={handlers.isSyncColorsEnabled()} /> , domNode, @@ -309,6 +311,7 @@ export function XYChart({ onClickValue, onSelectRange, renderMode, + syncColors, }: XYChartRenderProps) { const { legend, layers, fittingFunction, gridlinesVisibilitySettings, valueLabels } = args; const chartTheme = chartsThemeService.useChartsTheme(); @@ -681,6 +684,7 @@ export function XYChart({ maxDepth: 1, behindText: false, totalSeries: colorAssignment.totalSeriesCount, + syncColors, }, palette.params ); From 57d3349638f0f5fc1fdc550053076deade64ebdb Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 22 Dec 2020 19:39:30 +0300 Subject: [PATCH 10/72] Remove legacy src/plugins/charts -> src/plugins/discover cyclic dependencies (#86758) Part of #84750 --- src/dev/run_find_plugins_with_circular_deps.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dev/run_find_plugins_with_circular_deps.ts b/src/dev/run_find_plugins_with_circular_deps.ts index 9fa2d28b8d5c5..5afb8df8502df 100644 --- a/src/dev/run_find_plugins_with_circular_deps.ts +++ b/src/dev/run_find_plugins_with_circular_deps.ts @@ -31,7 +31,6 @@ interface Options { type CircularDepList = Set; const allowedList: CircularDepList = new Set([ - 'src/plugins/charts -> src/plugins/discover', 'src/plugins/vis_default_editor -> src/plugins/visualizations', 'src/plugins/visualizations -> src/plugins/visualize', 'x-pack/plugins/actions -> x-pack/plugins/case', From 1d856c39b5aaaee23cae0f0cbeea1c475af9273f Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Tue, 22 Dec 2020 11:41:35 -0500 Subject: [PATCH 11/72] Fix Add From Library Flyout Staying Open (#86698) * Fixed add from library flyout staying open after save or cancel --- ...ns-embeddable-public.openaddpanelflyout.md | 4 +- .../application/top_nav/dashboard_top_nav.tsx | 41 ++++++++++++++----- .../add_panel/open_add_panel_flyout.tsx | 7 ++-- src/plugins/embeddable/public/public.api.md | 3 +- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md index ce97f79b4beb9..add4646375359 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md @@ -14,7 +14,7 @@ export declare function openAddPanelFlyout(options: { overlays: OverlayStart; notifications: NotificationsStart; SavedObjectFinder: React.ComponentType; -}): Promise; +}): OverlayRef; ``` ## Parameters @@ -25,5 +25,5 @@ export declare function openAddPanelFlyout(options: { Returns: -`Promise` +`OverlayRef` diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index a9ed46160ef77..87ccbf29b99f7 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -57,10 +57,12 @@ import { showOptionsPopover } from './show_options_popover'; import { TopNavIds } from './top_nav_ids'; import { ShowShareModal } from './show_share_modal'; import { PanelToolbar } from './panel_toolbar'; +import { OverlayRef } from '../../../../../core/public'; import { DashboardContainer } from '..'; export interface DashboardTopNavState { chromeIsVisible: boolean; + addPanelOverlay?: OverlayRef; savedQuery?: SavedQuery; } @@ -111,14 +113,17 @@ export function DashboardTopNav({ const addFromLibrary = useCallback(() => { if (!isErrorEmbeddable(dashboardContainer)) { - openAddPanelFlyout({ - embeddable: dashboardContainer, - getAllFactories: embeddable.getEmbeddableFactories, - getFactory: embeddable.getEmbeddableFactory, - notifications: core.notifications, - overlays: core.overlays, - SavedObjectFinder: getSavedObjectFinder(core.savedObjects, uiSettings), - }); + setState((s) => ({ + ...s, + addPanelOverlay: openAddPanelFlyout({ + embeddable: dashboardContainer, + getAllFactories: embeddable.getEmbeddableFactories, + getFactory: embeddable.getEmbeddableFactory, + notifications: core.notifications, + overlays: core.overlays, + SavedObjectFinder: getSavedObjectFinder(core.savedObjects, uiSettings), + }), + })); } }, [ embeddable.getEmbeddableFactories, @@ -139,8 +144,16 @@ export function DashboardTopNav({ await factory.create({} as EmbeddableInput, dashboardContainer); }, [dashboardContainer, embeddable]); + const clearAddPanel = useCallback(() => { + if (state.addPanelOverlay) { + state.addPanelOverlay.close(); + setState((s) => ({ ...s, addPanelOverlay: undefined })); + } + }, [state.addPanelOverlay]); + const onChangeViewMode = useCallback( (newMode: ViewMode) => { + clearAddPanel(); const isPageRefresh = newMode === dashboardStateManager.getViewMode(); const isLeavingEditMode = !isPageRefresh && newMode === ViewMode.VIEW; const willLoseChanges = isLeavingEditMode && dashboardStateManager.getIsDirty(timefilter); @@ -178,7 +191,7 @@ export function DashboardTopNav({ } }); }, - [redirectTo, timefilter, core.overlays, savedDashboard.id, dashboardStateManager] + [redirectTo, timefilter, core.overlays, savedDashboard.id, dashboardStateManager, clearAddPanel] ); /** @@ -301,8 +314,16 @@ export function DashboardTopNav({ showCopyOnSave={lastDashboardId ? true : false} /> ); + clearAddPanel(); showSaveModal(dashboardSaveModal, core.i18n.Context); - }, [save, core.i18n.Context, savedObjectsTagging, dashboardStateManager, lastDashboardId]); + }, [ + save, + clearAddPanel, + lastDashboardId, + core.i18n.Context, + savedObjectsTagging, + dashboardStateManager, + ]); const runClone = useCallback(() => { const currentTitle = dashboardStateManager.getTitle(); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx index 867092b78ef7a..3363f556b418e 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx @@ -17,20 +17,20 @@ * under the License. */ import React from 'react'; -import { NotificationsStart, OverlayStart } from 'src/core/public'; +import { NotificationsStart, OverlayRef, OverlayStart } from 'src/core/public'; import { EmbeddableStart } from '../../../../../plugin'; import { toMountPoint } from '../../../../../../../kibana_react/public'; import { IContainer } from '../../../../containers'; import { AddPanelFlyout } from './add_panel_flyout'; -export async function openAddPanelFlyout(options: { +export function openAddPanelFlyout(options: { embeddable: IContainer; getFactory: EmbeddableStart['getEmbeddableFactory']; getAllFactories: EmbeddableStart['getEmbeddableFactories']; overlays: OverlayStart; notifications: NotificationsStart; SavedObjectFinder: React.ComponentType; -}) { +}): OverlayRef { const { embeddable, getFactory, @@ -59,4 +59,5 @@ export async function openAddPanelFlyout(options: { ownFocus: true, } ); + return flyoutSession; } diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index ab039d15a2ce6..b20d5866298d5 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -34,6 +34,7 @@ import { MaybePromise } from '@kbn/utility-types'; import { NotificationsStart as NotificationsStart_2 } from 'src/core/public'; import { Observable } from 'rxjs'; import { Optional } from '@kbn/utility-types'; +import { OverlayRef as OverlayRef_2 } from 'src/core/public'; import { OverlayStart as OverlayStart_2 } from 'src/core/public'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; @@ -717,7 +718,7 @@ export function openAddPanelFlyout(options: { overlays: OverlayStart_2; notifications: NotificationsStart_2; SavedObjectFinder: React.ComponentType; -}): Promise; +}): OverlayRef_2; // Warning: (ae-missing-release-tag) "OutputSpec" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // From bd13284bfebcf8d1fb70039287f88aa5e0f11ddc Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 22 Dec 2020 12:41:18 -0500 Subject: [PATCH 12/72] [ML] Ensure job group badge fonts are same color (#86674) * use badge component to keep group badge consistent. sort groups * use all colors now that badge component is used * set left margin on group badge wrapped in link to match badge margin * add badge colors to edit flyout job groups --- .../ml/common/util/group_color_utils.ts | 2 +- .../edit_job_flyout/tabs/job_details.js | 7 ++-- .../job_filter_bar/job_filter_bar.tsx | 1 - .../job_group/{index.js => index.ts} | 0 .../components/job_group/job_group.js | 34 ------------------- .../components/job_group/job_group.tsx | 15 ++++++++ .../components/jobs_list/job_id_link.tsx | 3 +- .../ml/server/models/job_service/groups.ts | 4 ++- 8 files changed, 26 insertions(+), 40 deletions(-) rename x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/{index.js => index.ts} (100%) delete mode 100644 x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/job_group.js create mode 100644 x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/job_group.tsx diff --git a/x-pack/plugins/ml/common/util/group_color_utils.ts b/x-pack/plugins/ml/common/util/group_color_utils.ts index 7105919274185..b5749641d2b57 100644 --- a/x-pack/plugins/ml/common/util/group_color_utils.ts +++ b/x-pack/plugins/ml/common/util/group_color_utils.ts @@ -13,7 +13,7 @@ const COLORS = [ euiVars.euiColorVis1, euiVars.euiColorVis2, euiVars.euiColorVis3, - // euiVars.euiColorVis4, // light pink, too hard to read with white text + euiVars.euiColorVis4, euiVars.euiColorVis5, euiVars.euiColorVis6, euiVars.euiColorVis7, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js index ec5ef6fce26b5..97b705177ed85 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js @@ -19,6 +19,7 @@ import { import { ml } from '../../../../../services/ml_api_service'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { tabColor } from '../../../../../../../common/util/group_color_utils'; export class JobDetails extends Component { constructor(props) { @@ -43,7 +44,7 @@ export class JobDetails extends Component { ml.jobs .groups() .then((resp) => { - const groups = resp.map((g) => ({ label: g.id })); + const groups = resp.map((g) => ({ label: g.id, color: tabColor(g.id) })); this.setState({ groups }); }) .catch((error) => { @@ -53,7 +54,9 @@ export class JobDetails extends Component { static getDerivedStateFromProps(props) { const selectedGroups = - props.jobGroups !== undefined ? props.jobGroups.map((g) => ({ label: g })) : []; + props.jobGroups !== undefined + ? props.jobGroups.map((g) => ({ label: g, color: tabColor(g) })) + : []; return { description: props.jobDescription, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx index 1b1bea889925f..71be812662299 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx @@ -17,7 +17,6 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -// @ts-ignore import { JobGroup } from '../job_group'; import { useMlKibana } from '../../../../contexts/kibana'; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/index.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/index.js rename to x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/index.ts diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/job_group.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/job_group.js deleted file mode 100644 index e8892c076c7a9..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/job_group.js +++ /dev/null @@ -1,34 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { tabColor } from '../../../../../../common/util/group_color_utils'; - -import PropTypes from 'prop-types'; -import React from 'react'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; - -export function JobGroup({ name }) { - return ( -
    - {name} -
    - ); -} -JobGroup.propTypes = { - name: PropTypes.string.isRequired, -}; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/job_group.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/job_group.tsx new file mode 100644 index 0000000000000..eab3e5ae3c1c7 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/job_group.tsx @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiBadge } from '@elastic/eui'; +import { tabColor } from '../../../../../../common/util/group_color_utils'; + +export const JobGroup: FC<{ name: string }> = ({ name }) => ( + + {name} + +); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_id_link.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_id_link.tsx index f1c82dbb83eb4..b81934630facd 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_id_link.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_id_link.tsx @@ -55,7 +55,8 @@ export const AnomalyDetectionJobIdLink = (props: AnomalyDetectionJobIdLinkProps) if (isGroupIdLink(props)) { return ( - + // Set margin-left to match EuiBadge (in JobGroup) built-in left margin for consistent badge spacing in management and plugin jobs list + ); diff --git a/x-pack/plugins/ml/server/models/job_service/groups.ts b/x-pack/plugins/ml/server/models/job_service/groups.ts index 59090f30ccca9..f6073ae7071b0 100644 --- a/x-pack/plugins/ml/server/models/job_service/groups.ts +++ b/x-pack/plugins/ml/server/models/job_service/groups.ts @@ -72,7 +72,9 @@ export function groupsProvider(mlClient: MlClient) { }); } - return Object.keys(groups).map((g) => groups[g]); + return Object.keys(groups) + .sort() + .map((g) => groups[g]); } async function updateGroups(jobs: Job[]) { From a2d13f6211f898cb6af947e0c29752daaf125587 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Tue, 22 Dec 2020 10:08:50 -0800 Subject: [PATCH 13/72] [DOCS] Fixes typo in saved search doc (#86808) --- docs/discover/search.asciidoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index 75c6fddb484ac..45f0df5bd773f 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -74,7 +74,7 @@ status codes, you could enter `status:[400 TO 499]`. codes and have an extension of `php` or `html`, you could enter `status:[400 TO 499] AND (extension:php OR extension:html)`. -IMPORTANT: When you use the Lucene Query Syntax in the *KQL* search bar, {kib} is unable to search on nested objects and perform aggregations across fields that contain nested objects. +IMPORTANT: When you use the Lucene Query Syntax in the *KQL* search bar, {kib} is unable to search on nested objects and perform aggregations across fields that contain nested objects. Using `include_in_parent` or `copy_to` as a workaround can cause {kib} to fail. For more detailed information about the Lucene query syntax, see the @@ -107,7 +107,8 @@ To save the current search: . Click *Save* in the Kibana toolbar. . Enter a name for the search and click *Save*. -To import, export, and delete saved searches, open the main menu, then click *Stack Management > Saved Ojbects*. +To import, export, and delete saved searches, open the main menu, +then click *Stack Management > Saved Objects*. ==== Open a saved search To load a saved search into Discover: From 4c17faa023f31f98c4a2664f7c56ac4502ef4437 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Tue, 22 Dec 2020 12:12:12 -0600 Subject: [PATCH 14/72] [ML] Fix zoom missing in Anomaly detection URLs (#86182) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../ml/common/types/ml_url_generator.ts | 28 ++++---- .../explorer_charts_container.js | 70 +++++++++++++------ .../explorer_charts_container.test.js | 5 ++ .../ml/public/application/util/chart_utils.js | 2 +- .../anomaly_detection_urls_generator.ts | 5 ++ .../ml_url_generator/ml_url_generator.test.ts | 2 +- 6 files changed, 72 insertions(+), 40 deletions(-) diff --git a/x-pack/plugins/ml/common/types/ml_url_generator.ts b/x-pack/plugins/ml/common/types/ml_url_generator.ts index fb432189c6dd3..3c70cf4c27b5d 100644 --- a/x-pack/plugins/ml/common/types/ml_url_generator.ts +++ b/x-pack/plugins/ml/common/types/ml_url_generator.ts @@ -146,30 +146,28 @@ export interface TimeSeriesExplorerGlobalState { refreshInterval?: RefreshInterval; } -export interface TimeSeriesExplorerAppState { - mlTimeSeriesExplorer?: { - forecastId?: string; - detectorIndex?: number; - entities?: Record; - zoom?: { - from?: string; - to?: string; - }; - functionDescription?: string; +export interface TimeSeriesExplorerParams { + forecastId?: string; + detectorIndex?: number; + entities?: Record; + zoom?: { + from?: string; + to?: string; }; + functionDescription?: string; +} +export interface TimeSeriesExplorerAppState { + mlTimeSeriesExplorer?: TimeSeriesExplorerParams; query?: any; } export interface TimeSeriesExplorerPageState - extends Pick, + extends TimeSeriesExplorerParams, + Pick, Pick { jobIds?: JobId[]; timeRange?: TimeRange; - detectorIndex?: number; - entities?: Record; - forecastId?: string; globalState?: MlCommonGlobalState; - functionDescription?: string; } export type TimeSeriesExplorerUrlState = MLPageState< diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js index ee5247d193e8a..774372f678c9b 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiButtonEmpty, @@ -54,14 +54,33 @@ function getChartId(series) { } // Wrapper for a single explorer chart -function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel, mlUrlGenerator }) { - const redirectToSingleMetricViewer = useCallback(async () => { - const singleMetricViewerLink = await getExploreSeriesLink(mlUrlGenerator, series); - addItemToRecentlyAccessed('timeseriesexplorer', series.jobId, singleMetricViewerLink); - - window.open(singleMetricViewerLink, '_blank'); - }, [mlUrlGenerator]); - +function ExplorerChartContainer({ + series, + severity, + tooManyBuckets, + wrapLabel, + mlUrlGenerator, + basePath, +}) { + const [explorerSeriesLink, setExplorerSeriesLink] = useState(); + + useEffect(() => { + let isCancelled = false; + const generateLink = async () => { + const singleMetricViewerLink = await getExploreSeriesLink(mlUrlGenerator, series); + if (!isCancelled) { + setExplorerSeriesLink(singleMetricViewerLink); + } + }; + generateLink(); + return () => { + isCancelled = true; + }; + }, [mlUrlGenerator, series]); + + const addToRecentlyAccessed = useCallback(() => { + addItemToRecentlyAccessed('timeseriesexplorer', series.jobId, explorerSeriesLink); + }, [explorerSeriesLink]); const { detectorLabel, entityFields } = series; const chartType = getChartType(series); @@ -111,16 +130,22 @@ function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel, m /> )} - - - - - + {explorerSeriesLink && ( + + {/* href needs to be full link with base path while ChromeRecentlyAccessed requires only relative path */} + {/* disabling because we need button to behave as link and to have a callback */} + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + + + + )}
    @@ -170,14 +195,13 @@ export const ExplorerChartsContainerUI = ({ }) => { const { services: { - application: { navigateToApp }, - + http: { basePath }, share: { urlGenerators: { getUrlGenerator }, }, }, } = kibana; - const mlUrlGenerator = getUrlGenerator(ML_APP_URL_GENERATOR); + const mlUrlGenerator = useMemo(() => getUrlGenerator(ML_APP_URL_GENERATOR), [getUrlGenerator]); // doesn't allow a setting of `columns={1}` when chartsPerRow would be 1. // If that's the case we trick it doing that with the following settings: @@ -201,8 +225,8 @@ export const ExplorerChartsContainerUI = ({ severity={severity} tooManyBuckets={tooManyBuckets} wrapLabel={wrapLabel} - navigateToApp={navigateToApp} mlUrlGenerator={mlUrlGenerator} + basePath={basePath.get()} /> ))} diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js index 2da212c8f2f29..96004516135d0 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js @@ -56,6 +56,11 @@ describe('ExplorerChartsContainer', () => { const kibanaContextMock = { services: { application: { navigateToApp: jest.fn() }, + http: { + basePath: { + get: jest.fn(), + }, + }, share: { urlGenerators: { getUrlGenerator: jest.fn() }, }, diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.js b/x-pack/plugins/ml/public/application/util/chart_utils.js index d142d2e246659..402c922a0034f 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.js +++ b/x-pack/plugins/ml/public/application/util/chart_utils.js @@ -264,7 +264,7 @@ export async function getExploreSeriesLink(mlUrlGenerator, series) { }, }, }, - excludeBasePath: false, + excludeBasePath: true, }); return url; } diff --git a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts index d2814bd63b0b0..b6a3ca0ce7139 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts @@ -163,6 +163,7 @@ export function createSingleMetricViewerUrl( entities, globalState, functionDescription, + zoom, } = params; let queryState: Partial = {}; @@ -193,6 +194,10 @@ export function createSingleMetricViewerUrl( mlTimeSeriesExplorer.functionDescription = functionDescription; } + if (zoom !== undefined) { + mlTimeSeriesExplorer.zoom = zoom; + } + appState.mlTimeSeriesExplorer = mlTimeSeriesExplorer; if (query) diff --git a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts index 7dcd901c2c0ef..21da0424cdca0 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts @@ -161,7 +161,7 @@ describe('MlUrlGenerator', () => { }, }); expect(url).toBe( - "/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(detectorIndex:0,entities:(mlcategory:'2')),query:(query_string:(analyze_wildcard:!t,query:'*'))))" + "/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(detectorIndex:0,entities:(mlcategory:'2'),zoom:(from:'2020-07-20T23:58:29.367Z',to:'2020-07-21T11:00:13.173Z')),query:(query_string:(analyze_wildcard:!t,query:'*'))))" ); }); }); From 61eb83b467dbaec1418ee8234814b905e47a2cf3 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Tue, 22 Dec 2020 12:22:56 -0600 Subject: [PATCH 15/72] [ML] Fix error callout for Anomaly detection jobs using runtime_mappings (#86407) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/ml/common/util/job_utils.ts | 29 +++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts index 6063511879448..4f4d9851c4957 100644 --- a/x-pack/plugins/ml/common/util/job_utils.ts +++ b/x-pack/plugins/ml/common/util/job_utils.ts @@ -49,6 +49,22 @@ export function calculateDatafeedFrequencyDefaultSeconds(bucketSpanSeconds: numb return freq; } +export function hasRuntimeMappings(job: CombinedJob): boolean { + const hasDatafeed = + typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0; + if (hasDatafeed) { + const runtimeMappings = + typeof job.datafeed_config.runtime_mappings === 'object' + ? Object.keys(job.datafeed_config.runtime_mappings) + : undefined; + + if (Array.isArray(runtimeMappings) && runtimeMappings.length > 0) { + return true; + } + } + return false; +} + export function isTimeSeriesViewJob(job: CombinedJob): boolean { return getSingleMetricViewerJobErrorMessage(job) === undefined; } @@ -94,10 +110,10 @@ export function isSourceDataChartableForDetector(job: CombinedJob, detectorIndex scriptFields.indexOf(dtr.over_field_name!) === -1; } - // We cannot plot the source data for some specific aggregation configurations const hasDatafeed = typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0; if (hasDatafeed) { + // We cannot plot the source data for some specific aggregation configurations const aggs = getDatafeedAggregations(job.datafeed_config); if (aggs !== undefined) { const aggBucketsName = getAggregationBucketsName(aggs); @@ -110,6 +126,11 @@ export function isSourceDataChartableForDetector(job: CombinedJob, detectorIndex } } } + + // We also cannot plot the source data if they datafeed uses any field defined by runtime_mappings + if (hasRuntimeMappings(job)) { + return false; + } } } @@ -149,6 +170,12 @@ export function isModelPlotChartableForDetector(job: Job, detectorIndex: number) // Returns a reason to indicate why the job configuration is not supported // if the result is undefined, that means the single metric job should be viewable export function getSingleMetricViewerJobErrorMessage(job: CombinedJob): string | undefined { + // if job has runtime mappings with no model plot + if (hasRuntimeMappings(job) && !job.model_plot_config?.enabled) { + return i18n.translate('xpack.ml.timeSeriesJob.jobWithRunTimeMessage', { + defaultMessage: 'the datafeed contains runtime fields and model plot is disabled', + }); + } // only allow jobs with at least one detector whose function corresponds to // an ES aggregation which can be viewed in the single metric view and which // doesn't use a scripted field which can be very difficult or impossible to From a98550b4f1d741126cd57a3d43c48fc4156013ce Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Tue, 22 Dec 2020 11:23:54 -0700 Subject: [PATCH 16/72] Reporting usage collector fetch migration (#86675) --- .../reporting/server/usage/get_reporting_usage.ts | 15 +++++++++------ .../usage/reporting_usage_collector.test.ts | 2 +- .../server/usage/reporting_usage_collector.ts | 4 ++-- x-pack/plugins/reporting/server/usage/types.ts | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts index 1211d4c2cf1c3..6d7f5c2e367fa 100644 --- a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts +++ b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { LegacyAPICaller } from 'kibana/server'; +import { ElasticsearchClient, SearchResponse } from 'kibana/server'; import { ReportingConfig } from '../'; import { ExportTypesRegistry } from '../lib/export_types_registry'; import { GetLicense } from './'; @@ -18,7 +18,7 @@ import { KeyCountBucket, RangeStats, ReportingUsageType, - SearchResponse, + ReportingUsageSearchResponse, StatusByAppBucket, } from './types'; @@ -99,7 +99,9 @@ type RangeStatSets = Partial & { last7Days: Partial; }; -async function handleResponse(response: SearchResponse): Promise> { +type ESResponse = Partial>; + +async function handleResponse(response: ESResponse): Promise> { const buckets = get(response, 'aggregations.ranges.buckets'); if (!buckets) { return {}; @@ -118,7 +120,7 @@ async function handleResponse(response: SearchResponse): Promise { const reportingIndex = config.get('index'); @@ -165,8 +167,9 @@ export async function getReportingUsage( }; const featureAvailability = await getLicense(); - return callCluster('search', params) - .then((response: SearchResponse) => handleResponse(response)) + return esClient + .search(params) + .then(({ body: response }) => handleResponse(response)) .then( (usage: Partial): ReportingUsageType => { // Allow this to explicitly throw an exception if/when this config is deprecated, diff --git a/x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts b/x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts index 7cae5e9b6f956..8b0c442c12b97 100644 --- a/x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts +++ b/x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts @@ -59,7 +59,7 @@ const getResponseMock = (base = {}) => base; const getMockFetchClients = (resp: any) => { const fetchParamsMock = createCollectorFetchContextMock(); - fetchParamsMock.callCluster.mockResolvedValue(resp); + fetchParamsMock.esClient.search = jest.fn().mockResolvedValue({ body: resp }); return fetchParamsMock; }; describe('license checks', () => { diff --git a/x-pack/plugins/reporting/server/usage/reporting_usage_collector.ts b/x-pack/plugins/reporting/server/usage/reporting_usage_collector.ts index f4209730b68ce..547c331784c5f 100644 --- a/x-pack/plugins/reporting/server/usage/reporting_usage_collector.ts +++ b/x-pack/plugins/reporting/server/usage/reporting_usage_collector.ts @@ -26,9 +26,9 @@ export function getReportingUsageCollector( ) { return usageCollection.makeUsageCollector({ type: 'reporting', - fetch: ({ callCluster }: CollectorFetchContext) => { + fetch: ({ esClient }: CollectorFetchContext) => { const config = reporting.getConfig(); - return getReportingUsage(config, getLicense, callCluster, exportTypesRegistry); + return getReportingUsage(config, getLicense, esClient, exportTypesRegistry); }, isReady, schema: reportingSchema, diff --git a/x-pack/plugins/reporting/server/usage/types.ts b/x-pack/plugins/reporting/server/usage/types.ts index 1ff680eff8eaf..fe7838240f2fa 100644 --- a/x-pack/plugins/reporting/server/usage/types.ts +++ b/x-pack/plugins/reporting/server/usage/types.ts @@ -152,7 +152,7 @@ export interface AggregationResultBuckets { doc_count: number; } -export interface SearchResponse { +export interface ReportingUsageSearchResponse { aggregations: { ranges: { buckets: { From 24db3a00707ac84c3c8af76a31922ee61902945a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Tue, 22 Dec 2020 19:36:02 +0100 Subject: [PATCH 17/72] [Logs UI] Fix initial selection of log threshold alert condition field if missing from mapping (#86488) This avoid selecting the `log.level` field as the default in log threshold alerts if it is not present in the mapping of at least one source index. --- .../alerting/logs/log_threshold/types.ts | 74 ++++++--- .../http_api/log_alerts/chart_preview_data.ts | 8 +- x-pack/plugins/infra/common/utility_types.ts | 3 + .../components/expression_editor/criteria.tsx | 79 ++++------ .../expression_editor/criterion.tsx | 28 ++-- .../criterion_preview_chart.tsx | 4 +- .../components/expression_editor/editor.tsx | 140 +++++++++++------- .../expression_editor/type_switcher.tsx | 6 +- .../log_threshold/log_threshold_alert_type.ts | 7 +- .../alerting/log_threshold/validation.ts | 62 ++++---- .../containers/logs/log_source/log_source.ts | 2 + .../log_threshold/log_threshold_executor.ts | 18 +-- .../register_log_threshold_alert_type.ts | 4 +- 13 files changed, 254 insertions(+), 181 deletions(-) diff --git a/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts b/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts index c505a234c7b2b..5f2e355ca3a47 100644 --- a/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts +++ b/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts @@ -105,29 +105,38 @@ const ThresholdRT = rt.type({ export type Threshold = rt.TypeOf; -export const CriterionRT = rt.type({ +export const criterionRT = rt.type({ field: rt.string, comparator: ComparatorRT, value: rt.union([rt.string, rt.number]), }); +export type Criterion = rt.TypeOf; -export type Criterion = rt.TypeOf; -export const criteriaRT = rt.array(CriterionRT); -export type Criteria = rt.TypeOf; +export const partialCriterionRT = rt.partial(criterionRT.props); +export type PartialCriterion = rt.TypeOf; -export const countCriteriaRT = criteriaRT; +export const countCriteriaRT = rt.array(criterionRT); export type CountCriteria = rt.TypeOf; -export const ratioCriteriaRT = rt.tuple([criteriaRT, criteriaRT]); +export const partialCountCriteriaRT = rt.array(partialCriterionRT); +export type PartialCountCriteria = rt.TypeOf; + +export const ratioCriteriaRT = rt.tuple([countCriteriaRT, countCriteriaRT]); export type RatioCriteria = rt.TypeOf; -export const TimeUnitRT = rt.union([ +export const partialRatioCriteriaRT = rt.tuple([partialCountCriteriaRT, partialCountCriteriaRT]); +export type PartialRatioCriteria = rt.TypeOf; + +export const partialCriteriaRT = rt.union([partialCountCriteriaRT, partialRatioCriteriaRT]); +export type PartialCriteria = rt.TypeOf; + +export const timeUnitRT = rt.union([ rt.literal('s'), rt.literal('m'), rt.literal('h'), rt.literal('d'), ]); -export type TimeUnit = rt.TypeOf; +export type TimeUnit = rt.TypeOf; export const timeSizeRT = rt.number; export const groupByRT = rt.array(rt.string); @@ -136,15 +145,18 @@ const RequiredAlertParamsRT = rt.type({ // NOTE: "count" would be better named as "threshold", but this would require a // migration of encrypted saved objects, so we'll keep "count" until it's problematic. count: ThresholdRT, - timeUnit: TimeUnitRT, + timeUnit: timeUnitRT, timeSize: timeSizeRT, }); +const partialRequiredAlertParamsRT = rt.partial(RequiredAlertParamsRT.props); +export type PartialRequiredAlertParams = rt.TypeOf; + const OptionalAlertParamsRT = rt.partial({ groupBy: groupByRT, }); -export const alertParamsRT = rt.intersection([ +export const countAlertParamsRT = rt.intersection([ rt.type({ criteria: countCriteriaRT, ...RequiredAlertParamsRT.props, @@ -153,8 +165,18 @@ export const alertParamsRT = rt.intersection([ ...OptionalAlertParamsRT.props, }), ]); +export type CountAlertParams = rt.TypeOf; -export type CountAlertParams = rt.TypeOf; +export const partialCountAlertParamsRT = rt.intersection([ + rt.type({ + criteria: partialCountCriteriaRT, + ...RequiredAlertParamsRT.props, + }), + rt.partial({ + ...OptionalAlertParamsRT.props, + }), +]); +export type PartialCountAlertParams = rt.TypeOf; export const ratioAlertParamsRT = rt.intersection([ rt.type({ @@ -165,13 +187,29 @@ export const ratioAlertParamsRT = rt.intersection([ ...OptionalAlertParamsRT.props, }), ]); - export type RatioAlertParams = rt.TypeOf; -export const AlertParamsRT = rt.union([alertParamsRT, ratioAlertParamsRT]); -export type AlertParams = rt.TypeOf; +export const partialRatioAlertParamsRT = rt.intersection([ + rt.type({ + criteria: partialRatioCriteriaRT, + ...RequiredAlertParamsRT.props, + }), + rt.partial({ + ...OptionalAlertParamsRT.props, + }), +]); +export type PartialRatioAlertParams = rt.TypeOf; + +export const alertParamsRT = rt.union([countAlertParamsRT, ratioAlertParamsRT]); +export type AlertParams = rt.TypeOf; + +export const partialAlertParamsRT = rt.union([ + partialCountAlertParamsRT, + partialRatioAlertParamsRT, +]); +export type PartialAlertParams = rt.TypeOf; -export const isRatioAlert = (criteria: AlertParams['criteria']): criteria is RatioCriteria => { +export const isRatioAlert = (criteria: PartialCriteria): criteria is PartialRatioCriteria => { return criteria.length > 0 && Array.isArray(criteria[0]) ? true : false; }; @@ -179,11 +217,13 @@ export const isRatioAlertParams = (params: AlertParams): params is RatioAlertPar return isRatioAlert(params.criteria); }; -export const getNumerator = (criteria: RatioCriteria): Criteria => { +export const getNumerator = (criteria: C): C[0] => { return criteria[0]; }; -export const getDenominator = (criteria: RatioCriteria): Criteria => { +export const getDenominator = ( + criteria: C +): C[1] => { return criteria[1]; }; diff --git a/x-pack/plugins/infra/common/http_api/log_alerts/chart_preview_data.ts b/x-pack/plugins/infra/common/http_api/log_alerts/chart_preview_data.ts index 3226287d4cbde..90547e6812225 100644 --- a/x-pack/plugins/infra/common/http_api/log_alerts/chart_preview_data.ts +++ b/x-pack/plugins/infra/common/http_api/log_alerts/chart_preview_data.ts @@ -6,8 +6,8 @@ import * as rt from 'io-ts'; import { - criteriaRT, - TimeUnitRT, + countCriteriaRT, + timeUnitRT, timeSizeRT, groupByRT, } from '../../alerting/logs/log_threshold/types'; @@ -42,8 +42,8 @@ export type GetLogAlertsChartPreviewDataSuccessResponsePayload = rt.TypeOf< export const getLogAlertsChartPreviewDataAlertParamsSubsetRT = rt.intersection([ rt.type({ - criteria: criteriaRT, - timeUnit: TimeUnitRT, + criteria: countCriteriaRT, + timeUnit: timeUnitRT, timeSize: timeSizeRT, }), rt.partial({ diff --git a/x-pack/plugins/infra/common/utility_types.ts b/x-pack/plugins/infra/common/utility_types.ts index 93fc9b729ca74..6bd784fed9308 100644 --- a/x-pack/plugins/infra/common/utility_types.ts +++ b/x-pack/plugins/infra/common/utility_types.ts @@ -43,3 +43,6 @@ export type DeepPartial = T extends any[] interface DeepPartialArray extends Array> {} type DeepPartialObject = { [P in keyof T]+?: DeepPartial }; + +export type ObjectEntry = [keyof T, T[keyof T]]; +export type ObjectEntries = Array>; diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx index 3c474ee1d0ec6..555ac905d2963 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx @@ -11,12 +11,11 @@ import { i18n } from '@kbn/i18n'; import { IFieldType } from 'src/plugins/data/public'; import { Criterion } from './criterion'; import { - AlertParams, - Comparator, - Criteria as CriteriaType, - Criterion as CriterionType, - CountCriteria as CountCriteriaType, - RatioCriteria as RatioCriteriaType, + PartialAlertParams, + PartialCountCriteria as PartialCountCriteriaType, + PartialCriteria as PartialCriteriaType, + PartialCriterion as PartialCriterionType, + PartialRatioCriteria as PartialRatioCriteriaType, isRatioAlert, getNumerator, getDenominator, @@ -25,8 +24,6 @@ import { Errors, CriterionErrors } from '../../validation'; import { ExpressionLike } from './editor'; import { CriterionPreview } from './criterion_preview_chart'; -const DEFAULT_CRITERIA = { field: 'log.level', comparator: Comparator.EQ, value: 'error' }; - const QueryAText = i18n.translate('xpack.infra.logs.alerting.threshold.ratioCriteriaQueryAText', { defaultMessage: 'Query A', }); @@ -37,11 +34,12 @@ const QueryBText = i18n.translate('xpack.infra.logs.alerting.threshold.ratioCrit interface SharedProps { fields: IFieldType[]; - criteria?: AlertParams['criteria']; + criteria?: PartialCriteriaType; + defaultCriterion: PartialCriterionType; errors: Errors['criteria']; - alertParams: Partial; + alertParams: PartialAlertParams; sourceId: string; - updateCriteria: (criteria: AlertParams['criteria']) => void; + updateCriteria: (criteria: PartialCriteriaType) => void; } type CriteriaProps = SharedProps; @@ -60,10 +58,10 @@ export const Criteria: React.FC = (props) => { interface CriteriaWrapperProps { alertParams: SharedProps['alertParams']; fields: SharedProps['fields']; - updateCriterion: (idx: number, params: Partial) => void; + updateCriterion: (idx: number, params: PartialCriterionType) => void; removeCriterion: (idx: number) => void; addCriterion: () => void; - criteria: CriteriaType; + criteria: PartialCountCriteriaType; errors: CriterionErrors; sourceId: SharedProps['sourceId']; isRatio?: boolean; @@ -118,29 +116,24 @@ const CriteriaWrapper: React.FC = (props) => { ); }; -interface RatioCriteriaProps { - alertParams: SharedProps['alertParams']; - fields: SharedProps['fields']; - criteria: RatioCriteriaType; - errors: Errors['criteria']; - sourceId: SharedProps['sourceId']; - updateCriteria: (criteria: AlertParams['criteria']) => void; +interface RatioCriteriaProps extends SharedProps { + criteria: PartialRatioCriteriaType; } const RatioCriteria: React.FC = (props) => { - const { criteria, errors, updateCriteria } = props; + const { criteria, defaultCriterion, errors, updateCriteria } = props; const handleUpdateNumeratorCriteria = useCallback( - (criteriaParam: CriteriaType) => { - const nextCriteria: RatioCriteriaType = [criteriaParam, getDenominator(criteria)]; + (criteriaParam: PartialCountCriteriaType) => { + const nextCriteria: PartialRatioCriteriaType = [criteriaParam, getDenominator(criteria)]; updateCriteria(nextCriteria); }, [updateCriteria, criteria] ); const handleUpdateDenominatorCriteria = useCallback( - (criteriaParam: CriteriaType) => { - const nextCriteria: RatioCriteriaType = [getNumerator(criteria), criteriaParam]; + (criteriaParam: PartialCountCriteriaType) => { + const nextCriteria: PartialRatioCriteriaType = [getNumerator(criteria), criteriaParam]; updateCriteria(nextCriteria); }, [updateCriteria, criteria] @@ -150,13 +143,13 @@ const RatioCriteria: React.FC = (props) => { updateCriterion: updateNumeratorCriterion, addCriterion: addNumeratorCriterion, removeCriterion: removeNumeratorCriterion, - } = useCriteriaState(getNumerator(criteria), handleUpdateNumeratorCriteria); + } = useCriteriaState(getNumerator(criteria), defaultCriterion, handleUpdateNumeratorCriteria); const { updateCriterion: updateDenominatorCriterion, addCriterion: addDenominatorCriterion, removeCriterion: removeDenominatorCriterion, - } = useCriteriaState(getDenominator(criteria), handleUpdateDenominatorCriteria); + } = useCriteriaState(getDenominator(criteria), defaultCriterion, handleUpdateDenominatorCriteria); return ( <> @@ -191,28 +184,17 @@ const RatioCriteria: React.FC = (props) => { ); }; -interface CountCriteriaProps { - alertParams: SharedProps['alertParams']; - fields: SharedProps['fields']; - criteria: CountCriteriaType; - errors: Errors['criteria']; - sourceId: SharedProps['sourceId']; - updateCriteria: (criteria: AlertParams['criteria']) => void; +interface CountCriteriaProps extends SharedProps { + criteria: PartialCountCriteriaType; } const CountCriteria: React.FC = (props) => { - const { criteria, updateCriteria, errors } = props; - - const handleUpdateCriteria = useCallback( - (criteriaParam: CriteriaType) => { - updateCriteria(criteriaParam); - }, - [updateCriteria] - ); + const { criteria, defaultCriterion, updateCriteria, errors } = props; const { updateCriterion, addCriterion, removeCriterion } = useCriteriaState( criteria, - handleUpdateCriteria + defaultCriterion, + updateCriteria ); return ( @@ -227,8 +209,9 @@ const CountCriteria: React.FC = (props) => { }; const useCriteriaState = ( - criteria: CriteriaType, - onUpdateCriteria: (criteria: CriteriaType) => void + criteria: PartialCountCriteriaType, + defaultCriterion: PartialCriterionType, + onUpdateCriteria: (criteria: PartialCountCriteriaType) => void ) => { const updateCriterion = useCallback( (idx, criterionParams) => { @@ -241,13 +224,13 @@ const useCriteriaState = ( ); const addCriterion = useCallback(() => { - const nextCriteria = criteria ? [...criteria, DEFAULT_CRITERIA] : [DEFAULT_CRITERIA]; + const nextCriteria = [...criteria, defaultCriterion]; onUpdateCriteria(nextCriteria); - }, [criteria, onUpdateCriteria]); + }, [criteria, defaultCriterion, onUpdateCriteria]); const removeCriterion = useCallback( (idx) => { - const nextCriteria = criteria.filter((criterion, index) => { + const nextCriteria = criteria.filter((_criterion, index) => { return index !== idx; }); onUpdateCriteria(nextCriteria); diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx index b2992ead3ea1b..9763a973d2fbd 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion.tsx @@ -90,7 +90,7 @@ const getFieldInfo = (fields: IFieldType[], fieldName: string): IFieldType | und interface Props { idx: number; fields: IFieldType[]; - criterion: CriterionType; + criterion: Partial; updateCriterion: (idx: number, params: Partial) => void; removeCriterion: (idx: number) => void; canDelete: boolean; @@ -116,7 +116,11 @@ export const Criterion: React.FC = ({ }, [fields]); const fieldInfo: IFieldType | undefined = useMemo(() => { - return getFieldInfo(fields, criterion.field); + if (criterion.field) { + return getFieldInfo(fields, criterion.field); + } else { + return undefined; + } }, [fields, criterion]); const compatibleComparatorOptions = useMemo(() => { @@ -129,10 +133,8 @@ export const Criterion: React.FC = ({ const nextFieldInfo = getFieldInfo(fields, fieldName); // If the field information we're dealing with has changed, reset the comparator and value. if ( - fieldInfo && - nextFieldInfo && - (fieldInfo.type !== nextFieldInfo.type || - fieldInfo.aggregatable !== nextFieldInfo.aggregatable) + fieldInfo?.type !== nextFieldInfo?.type || + fieldInfo?.aggregatable !== nextFieldInfo?.aggregatable ) { const compatibleComparators = getCompatibleComparatorsForField(nextFieldInfo); updateCriterion(idx, { @@ -160,7 +162,7 @@ export const Criterion: React.FC = ({ idx === 0 ? firstCriterionFieldPrefix : successiveCriterionFieldPrefix } uppercase={true} - value={criterion.field} + value={criterion.field ?? 'a chosen field'} isActive={isFieldPopoverOpen} color={errors.field.length === 0 ? 'secondary' : 'danger'} onClick={(e) => { @@ -180,7 +182,8 @@ export const Criterion: React.FC = ({ 0} error={errors.field}> @@ -194,9 +197,11 @@ export const Criterion: React.FC = ({ button={ = ({ 0} error={errors.comparator}> updateCriterion(idx, { comparator: e.target.value as Comparator }) diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx index 47dc419022880..cb759afa66d5c 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx @@ -34,7 +34,7 @@ import { NUM_BUCKETS, } from '../../../common/criterion_preview_chart/criterion_preview_chart'; import { - AlertParams, + PartialAlertParams, Threshold, Criterion, Comparator, @@ -50,7 +50,7 @@ import { decodeOrThrow } from '../../../../../common/runtime_types'; const GROUP_LIMIT = 5; interface Props { - alertParams: Partial; + alertParams: PartialAlertParams; chartCriterion: Partial; sourceId: string; showThreshold: boolean; diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx index 854363aacca5c..f69ca798c01b0 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx @@ -4,25 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo, useState } from 'react'; +import { EuiButton, EuiCallOut, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { EuiLoadingSpinner, EuiSpacer, EuiButton, EuiCallOut } from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; import useMount from 'react-use/lib/useMount'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -import { GroupByExpression } from '../../../common/group_by_expression/group_by_expression'; -import { ForLastExpression } from '../../../../../../triggers_actions_ui/public'; import { - AlertParams, + AlertTypeParamsExpressionProps, + ForLastExpression, +} from '../../../../../../triggers_actions_ui/public'; +import { + PartialAlertParams, Comparator, - ThresholdType, isRatioAlert, + PartialCriteria as PartialCriteriaType, + ThresholdType, + timeUnitRT, } from '../../../../../common/alerting/logs/log_threshold/types'; -import { Threshold } from './threshold'; +import { decodeOrThrow } from '../../../../../common/runtime_types'; +import { ObjectEntries } from '../../../../../common/utility_types'; +import { + LogIndexField, + LogSourceProvider, + useLogSourceContext, +} from '../../../../containers/logs/log_source'; +import { useSourceId } from '../../../../containers/source_id'; +import { GroupByExpression } from '../../../common/group_by_expression/group_by_expression'; +import { errorsRT } from '../../validation'; import { Criteria } from './criteria'; +import { Threshold } from './threshold'; import { TypeSwitcher } from './type_switcher'; -import { useSourceId } from '../../../../containers/source_id'; -import { LogSourceProvider, useLogSourceContext } from '../../../../containers/logs/log_source'; -import { Errors } from '../../validation'; export interface ExpressionCriteria { field?: string; @@ -34,45 +45,46 @@ interface LogsContextMeta { isInternal?: boolean; } -interface Props { - errors: Errors; - alertParams: Partial; - setAlertParams(key: string, value: any): void; - setAlertProperty(key: string, value: any): void; - sourceId: string; - metadata: LogsContextMeta; -} - -const DEFAULT_CRITERIA = { field: 'log.level', comparator: Comparator.EQ, value: 'error' }; - const DEFAULT_BASE_EXPRESSION = { timeSize: 5, timeUnit: 'm', }; -const DEFAULT_COUNT_EXPRESSION = { +const DEFAULT_FIELD = 'log.level'; + +const createDefaultCriterion = ( + availableFields: LogIndexField[], + value: ExpressionCriteria['value'] +) => + availableFields.some((availableField) => availableField.name === DEFAULT_FIELD) + ? { field: DEFAULT_FIELD, comparator: Comparator.EQ, value } + : { field: undefined, comparator: undefined, value: undefined }; + +const createDefaultCountAlertParams = (availableFields: LogIndexField[]) => ({ ...DEFAULT_BASE_EXPRESSION, count: { value: 75, comparator: Comparator.GT, }, - criteria: [DEFAULT_CRITERIA], -}; + criteria: [createDefaultCriterion(availableFields, 'error')], +}); -const DEFAULT_RATIO_EXPRESSION = { +const createDefaultRatioAlertParams = (availableFields: LogIndexField[]) => ({ ...DEFAULT_BASE_EXPRESSION, count: { value: 2, comparator: Comparator.GT, }, criteria: [ - [DEFAULT_CRITERIA], - [{ field: 'log.level', comparator: Comparator.EQ, value: 'warning' }], + createDefaultCriterion(availableFields, 'error'), + createDefaultCriterion([], 'warning'), ], -}; +}); -export const ExpressionEditor: React.FC = (props) => { - const isInternal = props.metadata?.isInternal; +export const ExpressionEditor: React.FC< + AlertTypeParamsExpressionProps +> = (props) => { + const isInternal = props.metadata?.isInternal ?? false; const [sourceId] = useSourceId(); const { http } = useKibana().services; @@ -80,12 +92,12 @@ export const ExpressionEditor: React.FC = (props) => { <> {isInternal ? ( - + ) : ( - + )} @@ -93,7 +105,7 @@ export const ExpressionEditor: React.FC = (props) => { ); }; -export const SourceStatusWrapper: React.FC = (props) => { +export const SourceStatusWrapper: React.FC = ({ children }) => { const { initialize, isLoadingSourceStatus, @@ -101,7 +113,6 @@ export const SourceStatusWrapper: React.FC = (props) => { hasFailedLoadingSourceStatus, loadSourceStatus, } = useLogSourceContext(); - const { children } = props; useMount(() => { initialize(); @@ -136,16 +147,19 @@ export const SourceStatusWrapper: React.FC = (props) => { ); }; -export const Editor: React.FC = (props) => { - const { setAlertParams, alertParams, errors, sourceId } = props; +export const Editor: React.FC< + AlertTypeParamsExpressionProps +> = (props) => { + const { setAlertParams, alertParams, errors } = props; const [hasSetDefaults, setHasSetDefaults] = useState(false); - const { sourceStatus } = useLogSourceContext(); - useMount(() => { - for (const [key, value] of Object.entries({ ...DEFAULT_COUNT_EXPRESSION, ...alertParams })) { - setAlertParams(key, value); - } - setHasSetDefaults(true); - }); + const { sourceId, sourceStatus } = useLogSourceContext(); + + const { + criteria: criteriaErrors, + threshold: thresholdErrors, + timeSizeUnit: timeSizeUnitErrors, + timeWindowSize: timeWindowSizeErrors, + } = useMemo(() => decodeOrThrow(errorsRT)(errors), [errors]); const supportedFields = useMemo(() => { if (sourceStatus?.logIndexFields) { @@ -176,7 +190,7 @@ export const Editor: React.FC = (props) => { ); const updateCriteria = useCallback( - (criteria: AlertParams['criteria']) => { + (criteria: PartialCriteriaType) => { setAlertParams('criteria', criteria); }, [setAlertParams] @@ -191,7 +205,9 @@ export const Editor: React.FC = (props) => { const updateTimeUnit = useCallback( (tu: string) => { - setAlertParams('timeUnit', tu); + if (timeUnitRT.is(tu)) { + setAlertParams('timeUnit', tu); + } }, [setAlertParams] ); @@ -203,20 +219,31 @@ export const Editor: React.FC = (props) => { [setAlertParams] ); + const defaultCountAlertParams = useMemo(() => createDefaultCountAlertParams(supportedFields), [ + supportedFields, + ]); + const updateType = useCallback( (type: ThresholdType) => { - const defaults = type === 'count' ? DEFAULT_COUNT_EXPRESSION : DEFAULT_RATIO_EXPRESSION; + const defaults = + type === 'count' ? defaultCountAlertParams : createDefaultRatioAlertParams(supportedFields); // Reset properties that don't make sense switching from one context to the other - for (const [key, value] of Object.entries({ - criteria: defaults.criteria, - count: defaults.count, - })) { - setAlertParams(key, value); - } + setAlertParams('count', defaults.count); + setAlertParams('criteria', defaults.criteria); }, - [setAlertParams] + [defaultCountAlertParams, setAlertParams, supportedFields] ); + useMount(() => { + const newAlertParams = { ...defaultCountAlertParams, ...alertParams }; + for (const [key, value] of Object.entries(newAlertParams) as ObjectEntries< + typeof newAlertParams + >) { + setAlertParams(key, value); + } + setHasSetDefaults(true); + }); + // Wait until the alert param defaults have been set if (!hasSetDefaults) return null; @@ -224,7 +251,8 @@ export const Editor: React.FC = (props) => { = (props) => { comparator={alertParams.count?.comparator} value={alertParams.count?.value} updateThreshold={updateThreshold} - errors={errors.threshold} + errors={thresholdErrors} /> = (props) => { timeWindowUnit={alertParams.timeUnit} onChangeWindowSize={updateTimeSize} onChangeWindowUnit={updateTimeUnit} - errors={{ timeWindowSize: errors.timeWindowSize, timeSizeUnit: errors.timeSizeUnit }} + errors={{ timeWindowSize: timeWindowSizeErrors, timeSizeUnit: timeSizeUnitErrors }} /> void; } -const getThresholdType = (criteria: AlertParams['criteria']): ThresholdType => { +const getThresholdType = (criteria: PartialCriteria): ThresholdType => { return isRatioAlert(criteria) ? 'ratio' : 'count'; }; diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts index 7154a77496b81..6cdb81155ec9a 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts +++ b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts @@ -6,10 +6,13 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; -import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID } from '../../../common/alerting/logs/log_threshold/types'; +import { + LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, + PartialAlertParams, +} from '../../../common/alerting/logs/log_threshold/types'; import { validateExpression } from './validation'; -export function getAlertType(): AlertTypeModel { +export function getAlertType(): AlertTypeModel { return { id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, description: i18n.translate('xpack.infra.logs.alertFlyout.alertDescription', { diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/validation.ts b/x-pack/plugins/infra/public/alerting/log_threshold/validation.ts index 6630b3d079141..24d373558008d 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/validation.ts +++ b/x-pack/plugins/infra/public/alerting/log_threshold/validation.ts @@ -5,45 +5,53 @@ */ import { i18n } from '@kbn/i18n'; +import * as rt from 'io-ts'; import { isNumber, isFinite } from 'lodash'; -import { ValidationResult } from '../../../../triggers_actions_ui/public'; +import { IErrorObject, ValidationResult } from '../../../../triggers_actions_ui/public'; import { - AlertParams, - Criteria, - RatioCriteria, + PartialCountCriteria, isRatioAlert, getNumerator, getDenominator, + PartialRequiredAlertParams, + PartialCriteria, } from '../../../common/alerting/logs/log_threshold/types'; -export interface CriterionErrors { - [id: string]: { - field: string[]; - comparator: string[]; - value: string[]; - }; -} +export const criterionErrorRT = rt.type({ + field: rt.array(rt.string), + comparator: rt.array(rt.string), + value: rt.array(rt.string), +}); -export interface Errors { - threshold: { - value: string[]; - }; +export const criterionErrorsRT = rt.record(rt.string, criterionErrorRT); + +export type CriterionErrors = rt.TypeOf; + +const alertingErrorRT: rt.Type = rt.recursion('AlertingError', () => + rt.record(rt.string, rt.union([rt.string, rt.array(rt.string), alertingErrorRT])) +); + +export const errorsRT = rt.type({ + threshold: rt.type({ + value: rt.array(rt.string), + }), // NOTE: The data structure for criteria errors isn't 100% // ideal but we need to conform to the interfaces that the alerting // framework expects. - criteria: { - [id: string]: CriterionErrors; - }; - timeWindowSize: string[]; - timeSizeUnit: string[]; -} + criteria: rt.record(rt.string, criterionErrorsRT), + timeWindowSize: rt.array(rt.string), + timeSizeUnit: rt.array(rt.string), +}); + +export type Errors = rt.TypeOf; export function validateExpression({ count, criteria, timeSize, - timeUnit, -}: Partial): ValidationResult { +}: PartialRequiredAlertParams & { + criteria: PartialCriteria; +}): ValidationResult { const validationResult = { errors: {} }; // NOTE: In the case of components provided by the Alerting framework the error property names @@ -79,7 +87,7 @@ export function validateExpression({ // Criteria validation if (criteria && criteria.length > 0) { - const getCriterionErrors = (_criteria: Criteria): CriterionErrors => { + const getCriterionErrors = (_criteria: PartialCountCriteria): CriterionErrors => { const _errors: CriterionErrors = {}; _criteria.forEach((criterion, idx) => { @@ -114,12 +122,12 @@ export function validateExpression({ }; if (!isRatioAlert(criteria)) { - const criteriaErrors = getCriterionErrors(criteria as Criteria); + const criteriaErrors = getCriterionErrors(criteria); errors.criteria[0] = criteriaErrors; } else { - const numeratorErrors = getCriterionErrors(getNumerator(criteria as RatioCriteria)); + const numeratorErrors = getCriterionErrors(getNumerator(criteria)); errors.criteria[0] = numeratorErrors; - const denominatorErrors = getCriterionErrors(getDenominator(criteria as RatioCriteria)); + const denominatorErrors = getCriterionErrors(getDenominator(criteria)); errors.criteria[1] = denominatorErrors; } } diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts index 879d2d95d7946..d7f40f603a9f7 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts @@ -9,6 +9,7 @@ import { useCallback, useMemo, useState } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; import type { HttpHandler } from 'src/core/public'; import { + LogIndexField, LogSourceConfiguration, LogSourceConfigurationProperties, LogSourceConfigurationPropertiesPatch, @@ -20,6 +21,7 @@ import { callFetchLogSourceStatusAPI } from './api/fetch_log_source_status'; import { callPatchLogSourceConfigurationAPI } from './api/patch_log_source_configuration'; export { + LogIndexField, LogSourceConfiguration, LogSourceConfigurationProperties, LogSourceConfigurationPropertiesPatch, diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index dccab5168fb60..09d7e482772c2 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -23,12 +23,12 @@ import { UngroupedSearchQueryResponseRT, UngroupedSearchQueryResponse, GroupedSearchQueryResponse, - AlertParamsRT, + alertParamsRT, isRatioAlertParams, hasGroupBy, getNumerator, getDenominator, - Criteria, + CountCriteria, CountAlertParams, RatioAlertParams, } from '../../../../common/alerting/logs/log_threshold/types'; @@ -67,7 +67,7 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) => const alertInstance = alertInstanceFactory(UNGROUPED_FACTORY_KEY); try { - const validatedParams = decodeOrThrow(AlertParamsRT)(params); + const validatedParams = decodeOrThrow(alertParamsRT)(params); if (!isRatioAlertParams(validatedParams)) { await executeAlert( @@ -174,7 +174,7 @@ async function executeRatioAlert( } const getESQuery = ( - alertParams: Omit & { criteria: Criteria }, + alertParams: Omit & { criteria: CountCriteria }, timestampField: string, indexPattern: string ) => { @@ -366,7 +366,7 @@ export const updateAlertInstance: AlertInstanceUpdater = (alertInstance, state, }; export const buildFiltersFromCriteria = ( - params: Pick & { criteria: Criteria }, + params: Pick & { criteria: CountCriteria }, timestampField: string ) => { const { timeSize, timeUnit, criteria } = params; @@ -417,7 +417,7 @@ export const buildFiltersFromCriteria = ( }; export const getGroupedESQuery = ( - params: Pick & { criteria: Criteria }, + params: Pick & { criteria: CountCriteria }, timestampField: string, index: string ): object | undefined => { @@ -475,7 +475,7 @@ export const getGroupedESQuery = ( }; export const getUngroupedESQuery = ( - params: Pick & { criteria: Criteria }, + params: Pick & { criteria: CountCriteria }, timestampField: string, index: string ): object => { @@ -509,7 +509,7 @@ type Filter = { [key in SupportedESQueryTypes]?: object; }; -const buildFiltersForCriteria = (criteria: Criteria) => { +const buildFiltersForCriteria = (criteria: CountCriteria) => { let filters: Filter[] = []; criteria.forEach((criterion) => { @@ -643,7 +643,7 @@ const getGroupedResults = async ( return compositeGroupBuckets; }; -const createConditionsMessageForCriteria = (criteria: Criteria) => { +const createConditionsMessageForCriteria = (criteria: CountCriteria) => { const parts = criteria.map((criterion, index) => { const { field, comparator, value } = criterion; return `${index === 0 ? '' : 'and'} ${field} ${comparator} ${value}`; diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts index 4703371f5e0de..e248d3b3ddcfa 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts @@ -8,7 +8,7 @@ import { PluginSetupContract } from '../../../../../alerts/server'; import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor'; import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, - AlertParamsRT, + alertParamsRT, } from '../../../../common/alerting/logs/log_threshold/types'; import { InfraBackendLibs } from '../../infra_types'; import { decodeOrThrow } from '../../../../common/runtime_types'; @@ -86,7 +86,7 @@ export async function registerLogThresholdAlertType( }), validate: { params: { - validate: (params) => decodeOrThrow(AlertParamsRT)(params), + validate: (params) => decodeOrThrow(alertParamsRT)(params), }, }, defaultActionGroupId: FIRED_ACTIONS.id, From c0d6bd95b78ad70f5df0f76bf69c6312f75c48fd Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Tue, 22 Dec 2020 12:59:05 -0600 Subject: [PATCH 18/72] Upgrade EUI to v31.0.0 (#86689) * eui to 31.0.0 * snapshot updates * remove obsolete snapshot --- package.json | 2 +- .../dashboard_empty_screen.test.tsx.snap | 4 ++-- .../__snapshots__/dashboard_listing.test.tsx.snap | 12 ++++++------ .../workpad_templates.stories.storyshot | 6 +----- .../__tests__/__snapshots__/no_data.test.js.snap | 4 ++-- .../__snapshots__/page_loading.test.js.snap | 2 +- .../__snapshots__/reset_session_page.test.tsx.snap | 2 +- .../__snapshots__/index.test.tsx.snap | 7 +------ yarn.lock | 8 ++++---- 9 files changed, 19 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 985998e54cb88..49787bb8ab34e 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "@elastic/datemath": "link:packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary", "@elastic/ems-client": "7.11.0", - "@elastic/eui": "30.6.0", + "@elastic/eui": "31.0.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "^9.0.1-kibana3", "@elastic/node-crypto": "1.2.1", diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap index bdfb1c45f351a..a50aadc12e6c0 100644 --- a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -597,7 +597,7 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = ` restrictWidth="500px" >
    @@ -218,7 +218,7 @@ exports[`after fetch hideWriteControls 1`] = ` restrictWidth={true} >
    @@ -358,7 +358,7 @@ exports[`after fetch initialFilter 1`] = ` restrictWidth={true} >
    @@ -497,7 +497,7 @@ exports[`after fetch renders all table rows 1`] = ` restrictWidth={true} >
    @@ -636,7 +636,7 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` restrictWidth={true} >
    @@ -775,7 +775,7 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` restrictWidth={true} >
    diff --git a/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot index 1f7105b80de4c..d267ba07078fe 100644 --- a/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot @@ -178,11 +178,7 @@ exports[`Storyshots components/WorkpadTemplates default 1`] = ` > - } + title="; Sorted in ascending order" > Template name diff --git a/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap index ffb1620b60fa3..8f820252d5b6b 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap @@ -2,7 +2,7 @@ exports[`NoData should show a default message if reason is unknown 1`] = `

    MockedFonts

    You do not have permission to access the requested page

    Either go back to the previous page or log in as a different user.

    "`; +exports[`ResetSessionPage renders as expected 1`] = `"MockedFonts

    You do not have permission to access the requested page

    Either go back to the previous page or log in as a different user.

    "`; diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap index a6aa844919709..a2a9c30ca4e1c 100644 --- a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap @@ -460,12 +460,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta "euiRangeTrackHeight": "2px", "euiRangeTrackRadius": "4px", "euiRangeTrackWidth": "100%", - "euiResizableButtonSizeModifiers": Object { - "sizeExtraLarge": "40px", - "sizeLarge": "24px", - "sizeMedium": "16px", - "sizeSmall": "12px", - }, + "euiResizableButtonSize": "16px", "euiResizableButtonTransitionSpeed": "150ms", "euiScrollBar": "16px", "euiScrollBarCorner": "6px", diff --git a/yarn.lock b/yarn.lock index de9efb26c384b..6fa3ea09731b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1439,10 +1439,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@30.6.0": - version "30.6.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-30.6.0.tgz#6653223223f52407ac05303825d9bd08382df1d5" - integrity sha512-40Jiy54MpJAx3lD3NSZZLkMkVySwKpX6RxIKnvT3somE95pwIjXrWB688m2nL2g05y7kNhjrhwfdctVzNXZENA== +"@elastic/eui@31.0.0": + version "31.0.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-31.0.0.tgz#7d17386c04a0ad343d70c3652902fcd3f46ed337" + integrity sha512-oj63HpQQKg/Cgwz5B0ZBQCkcgZiEdQzBT9PbmEiR/VRz5P0WqJpgZPyIF7jiFaFlGP1a9hPjkUTo+ramWNCpiw== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" From a75ca7abe9cce841514bd1abf14e65109812bc07 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Tue, 22 Dec 2020 13:12:06 -0600 Subject: [PATCH 19/72] [ML] Stabilize data visualizer functional tests (#86790) --- .../ml/data_visualizer_index_based.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts index 5fc5caf81c23b..fc9dd3d7b033a 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts @@ -33,25 +33,33 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ }, async assertTotalDocCountHeaderExist() { - await testSubjects.existOrFail(`mlDataVisualizerTotalDocCountHeader`); + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail(`mlDataVisualizerTotalDocCountHeader`); + }); }, async assertTotalDocCountChartExist() { - await testSubjects.existOrFail(`mlFieldDataDocumentCountChart`); + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail(`mlFieldDataDocumentCountChart`); + }); }, async assertFieldCountPanelExist() { - await testSubjects.existOrFail(`mlDataVisualizerFieldCountPanel`); + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail(`mlDataVisualizerFieldCountPanel`); + }); }, async assertMetricFieldsSummaryExist() { - await testSubjects.existOrFail(`mlDataVisualizerMetricFieldsSummary`); + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail(`mlDataVisualizerMetricFieldsSummary`); + }); }, async assertVisibleMetricFieldsCount(count: number) { const expectedCount = count.toString(); - await testSubjects.existOrFail('mlDataVisualizerVisibleMetricFieldsCount'); await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail('mlDataVisualizerVisibleMetricFieldsCount'); const actualCount = await testSubjects.getVisibleText( 'mlDataVisualizerVisibleMetricFieldsCount' ); @@ -64,8 +72,8 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ async assertTotalMetricFieldsCount(count: number) { const expectedCount = count.toString(); - await testSubjects.existOrFail('mlDataVisualizerMetricFieldsCount'); await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail('mlDataVisualizerMetricFieldsCount'); const actualCount = await testSubjects.getVisibleText( 'mlDataVisualizerVisibleMetricFieldsCount' ); @@ -78,8 +86,8 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ async assertVisibleFieldsCount(count: number) { const expectedCount = count.toString(); - await testSubjects.existOrFail('mlDataVisualizerVisibleFieldsCount'); await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail('mlDataVisualizerVisibleFieldsCount'); const actualCount = await testSubjects.getVisibleText('mlDataVisualizerVisibleFieldsCount'); expect(expectedCount).to.eql( expectedCount, @@ -90,8 +98,8 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ async assertTotalFieldsCount(count: number) { const expectedCount = count.toString(); - await testSubjects.existOrFail('mlDataVisualizerTotalFieldsCount'); await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail('mlDataVisualizerTotalFieldsCount'); const actualCount = await testSubjects.getVisibleText('mlDataVisualizerTotalFieldsCount'); expect(expectedCount).to.contain( expectedCount, @@ -101,11 +109,15 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ }, async assertFieldsSummaryExist() { - await testSubjects.existOrFail(`mlDataVisualizerFieldsSummary`); + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail(`mlDataVisualizerFieldsSummary`); + }); }, async assertDataVisualizerTableExist() { - await testSubjects.existOrFail(`mlDataVisualizerTable`); + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail(`mlDataVisualizerTable`); + }); }, async assertActionsPanelExists() { From 5e3c84ca05c292b16478454a927d33345dd9c552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 22 Dec 2020 20:27:17 +0100 Subject: [PATCH 20/72] useSavedDashboard: call toast methods in their context (#86803) --- .../public/application/hooks/use_saved_dashboard.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/plugins/dashboard/public/application/hooks/use_saved_dashboard.ts b/src/plugins/dashboard/public/application/hooks/use_saved_dashboard.ts index f0d8b5f5e000d..d946e00e1c8e4 100644 --- a/src/plugins/dashboard/public/application/hooks/use_saved_dashboard.ts +++ b/src/plugins/dashboard/public/application/hooks/use_saved_dashboard.ts @@ -36,7 +36,7 @@ export const useSavedDashboard = (savedDashboardId: string | undefined, history: // abstraction of service dependencies easier. const { indexPatterns } = data; const { recentlyAccessed: recentlyAccessedPaths, docTitle } = chrome; - const { addDanger: showDangerToast, addWarning: showWarningToast } = core.notifications.toasts; + const { toasts } = core.notifications; useEffect(() => { (async function loadSavedDashboard() { @@ -46,7 +46,7 @@ export const useSavedDashboard = (savedDashboardId: string | undefined, history: pathname: DashboardConstants.CREATE_NEW_DASHBOARD_URL, }); - showWarningToast(getDashboard60Warning()); + toasts.addWarning(getDashboard60Warning()); return; } @@ -63,7 +63,7 @@ export const useSavedDashboard = (savedDashboardId: string | undefined, history: setSavedDashboard(dashboard); } catch (error) { // E.g. a corrupt or deleted dashboard - showDangerToast(error.message); + toasts.addDanger(error.message); history.push(DashboardConstants.LANDING_PAGE_PATH); } })(); @@ -75,8 +75,7 @@ export const useSavedDashboard = (savedDashboardId: string | undefined, history: recentlyAccessedPaths, savedDashboardId, savedDashboards, - showDangerToast, - showWarningToast, + toasts, ]); return savedDashboard; From 97858d697b8367583632283b83d80d09bb69d70e Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Tue, 22 Dec 2020 20:40:40 +0100 Subject: [PATCH 21/72] Re-add support for configuring server.host: "0" (#86806) Support for this was lost in #85406. Fixes #86716 --- src/core/server/http/http_config.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 2bd296fe338ab..61a9b5f04b23f 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -195,7 +195,13 @@ export class HttpConfig { rawExternalUrlConfig: ExternalUrlConfig ) { this.autoListen = rawHttpConfig.autoListen; - this.host = rawHttpConfig.host; + // TODO: Consider dropping support for '0' in v8.0.0. This value is passed + // to hapi, which validates it. Prior to hapi v20, '0' was considered a + // valid host, however the validation logic internally in hapi was + // re-written for v20 and hapi no longer considers '0' a valid host. For + // details, see: + // https://github.com/elastic/kibana/issues/86716#issuecomment-749623781 + this.host = rawHttpConfig.host === '0' ? '0.0.0.0' : rawHttpConfig.host; this.port = rawHttpConfig.port; this.cors = rawHttpConfig.cors; this.customResponseHeaders = Object.entries(rawHttpConfig.customResponseHeaders ?? {}).reduce( From 978fa4325c900120a4605de4bbb957844466e723 Mon Sep 17 00:00:00 2001 From: Pete Hampton Date: Tue, 22 Dec 2020 19:55:40 +0000 Subject: [PATCH 22/72] Add run and alert count to task state. (#86776) --- .../server/lib/telemetry/task.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/task.ts b/x-pack/plugins/security_solution/server/lib/telemetry/task.ts index 28b8524f64516..a723cb9a3e637 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/task.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/task.ts @@ -36,6 +36,8 @@ export class TelemetryDiagTask { title: 'Security Solution Telemetry Diagnostics task', timeout: TelemetryDiagTaskConstants.TIMEOUT, createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => { + const { state } = taskInstance; + return { run: async () => { const executeTo = moment().utc().toISOString(); @@ -43,11 +45,13 @@ export class TelemetryDiagTask { executeTo, taskInstance.state?.lastExecutionTimestamp ); - await this.runTask(taskInstance.id, executeFrom, executeTo); + const hits = await this.runTask(taskInstance.id, executeFrom, executeTo); return { state: { lastExecutionTimestamp: executeTo, + lastDiagAlertCount: hits, + runs: (state.runs || 0) + 1, }, }; }, @@ -81,7 +85,7 @@ export class TelemetryDiagTask { schedule: { interval: TelemetryDiagTaskConstants.INTERVAL, }, - state: {}, + state: { runs: 0 }, params: { version: TelemetryDiagTaskConstants.VERSION }, }); } catch (e) { @@ -97,13 +101,13 @@ export class TelemetryDiagTask { this.logger.debug(`Running task ${taskId}`); if (taskId !== this.getTaskId()) { this.logger.debug(`Outdated task running: ${taskId}`); - return; + return 0; } const isOptedIn = await this.sender.isTelemetryOptedIn(); if (!isOptedIn) { this.logger.debug(`Telemetry is not opted-in.`); - return; + return 0; } const response = await this.sender.fetchDiagnosticAlerts(searchFrom, searchTo); @@ -111,11 +115,12 @@ export class TelemetryDiagTask { const hits = response.hits?.hits || []; if (!Array.isArray(hits) || !hits.length) { this.logger.debug('no diagnostic alerts retrieved'); - return; + return 0; } + this.logger.debug(`Received ${hits.length} diagnostic alerts`); const diagAlerts: TelemetryEvent[] = hits.map((h) => h._source); - this.logger.debug(`Received ${diagAlerts.length} diagnostic alerts`); this.sender.queueTelemetryEvents(diagAlerts); + return diagAlerts.length; }; } From 23c9c7e302dce25ba80b6de773b63d7afaf68a15 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Tue, 22 Dec 2020 15:41:33 -0500 Subject: [PATCH 23/72] fix uptime monitors donut chart labels (#86319) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/donut_chart.test.tsx.snap | 12 ++++++---- .../donut_chart_legend.test.tsx.snap | 21 ---------------- .../donut_chart_legend_row.test.tsx.snap | 1 + .../__tests__/donut_chart_legend.test.tsx | 24 +++++++++++++++---- .../common/charts/donut_chart_legend.tsx | 4 ++-- .../common/charts/donut_chart_legend_row.tsx | 2 +- 6 files changed, 32 insertions(+), 32 deletions(-) delete mode 100644 x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart_legend.test.tsx.snap diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap index 1a18cf5651bee..21d65f63783c5 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap @@ -490,8 +490,9 @@ exports[`DonutChart component renders a donut chart 1`] = ` - Up + Down - Down + Up - Up + Down - Down + Up - - - - -`; diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart_legend_row.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart_legend_row.test.tsx.snap index bc6033ea7109a..6e2a58cf528ed 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart_legend_row.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart_legend_row.test.tsx.snap @@ -15,6 +15,7 @@ exports[`DonutChartLegendRow passes appropriate props 1`] = ` Foo diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/donut_chart_legend.test.tsx b/x-pack/plugins/uptime/public/components/common/charts/__tests__/donut_chart_legend.test.tsx index 1b71b87884fb8..2ef02106e6e66 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/donut_chart_legend.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/donut_chart_legend.test.tsx @@ -3,14 +3,30 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; +import { renderWithIntl } from '@kbn/test/jest'; import { DonutChartLegend } from '../donut_chart_legend'; -import { shallowWithIntl } from '@kbn/test/jest'; -import React from 'react'; + +import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../../translations'; describe('DonutChartLegend', () => { it('applies valid props as expected', () => { - const wrapper = shallowWithIntl(); - expect(wrapper).toMatchSnapshot(); + const up = 45; + const down = 23; + const component = renderWithIntl(); + + expect( + component.find('[data-test-subj="xpack.uptime.snapshot.donutChart.up.label"]').text() + ).toBe(STATUS_UP_LABEL); + expect(component.find('[data-test-subj="xpack.uptime.snapshot.donutChart.up"]').text()).toBe( + `${up}` + ); + expect( + component.find('[data-test-subj="xpack.uptime.snapshot.donutChart.down.label"]').text() + ).toBe(STATUS_DOWN_LABEL); + expect(component.find('[data-test-subj="xpack.uptime.snapshot.donutChart.down"]').text()).toBe( + `${down}` + ); }); }); diff --git a/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend.tsx b/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend.tsx index f3b50895fff63..92b9c72e3f1e6 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend.tsx @@ -34,14 +34,14 @@ export const DonutChartLegend = ({ down, up }: Props) => { diff --git a/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend_row.tsx b/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend_row.tsx index fc67a86db3b48..0f637aff3bfa4 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend_row.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend_row.tsx @@ -31,7 +31,7 @@ export const DonutChartLegendRow = ({ color, content, message, 'data-test-subj': - + {message} From 59298c5f910f64a2940ff287ebe4afccb9d023e8 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Tue, 22 Dec 2020 17:01:39 -0500 Subject: [PATCH 24/72] [Security Solution] [Detections] Add "read index" privilege check on rule execution (#83134) * adds privilege check in rule execution function, need to abstract these lines into a util function to be used in create rules and use that check on the UI too * fixes tests * cleanup code, adds a unit test * set rule to failure status if the rule does not have read privileges to ANY of the index patterns provided --- .../signals/signal_rule_alert_type.test.ts | 64 +++++++++++++++++++ .../signals/signal_rule_alert_type.ts | 47 +++++++++++++- .../lib/detection_engine/signals/utils.ts | 14 ++++ 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 9c2ea0945297e..0ba2507e2e649 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -16,6 +16,7 @@ import { getListsClient, getExceptions, sortExceptionItems, + checkPrivileges, } from './utils'; import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; import { RuleExecutorOptions, SearchAfterAndBulkCreateReturnType } from './types'; @@ -42,6 +43,7 @@ jest.mock('./utils', () => { getListsClient: jest.fn(), getExceptions: jest.fn(), sortExceptionItems: jest.fn(), + checkPrivileges: jest.fn(), }; }); jest.mock('../notifications/schedule_notification_actions'); @@ -105,6 +107,7 @@ describe('rules_notification_alert_type', () => { find: jest.fn(), goingToRun: jest.fn(), error: jest.fn(), + partialFailure: jest.fn(), }; (ruleStatusServiceFactory as jest.Mock).mockReturnValue(ruleStatusService); (getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(0)); @@ -124,6 +127,21 @@ describe('rules_notification_alert_type', () => { searchAfterTimes: [], createdSignalsCount: 10, }); + (checkPrivileges as jest.Mock).mockImplementation((_, indices) => { + return { + index: indices.reduce( + (acc: { index: { [x: string]: { read: boolean } } }, index: string) => { + return { + [index]: { + read: true, + }, + ...acc, + }; + }, + {} + ), + }; + }); alertServices.callCluster.mockResolvedValue({ hits: { total: { value: 10 }, @@ -170,6 +188,52 @@ describe('rules_notification_alert_type', () => { }); }); + it('should set a partial failure for when rules cannot read ALL provided indices', async () => { + (checkPrivileges as jest.Mock).mockResolvedValueOnce({ + username: 'elastic', + has_all_requested: false, + cluster: {}, + index: { + 'myfa*': { + read: true, + }, + 'some*': { + read: false, + }, + }, + application: {}, + }); + payload.params.index = ['some*', 'myfa*']; + await alert.executor(payload); + expect(ruleStatusService.partialFailure).toHaveBeenCalled(); + expect(ruleStatusService.partialFailure.mock.calls[0][0]).toContain( + 'Missing required read permissions on indexes: ["some*"]' + ); + }); + + it('should set a failure status for when rules cannot read ANY provided indices', async () => { + (checkPrivileges as jest.Mock).mockResolvedValueOnce({ + username: 'elastic', + has_all_requested: false, + cluster: {}, + index: { + 'myfa*': { + read: false, + }, + 'some*': { + read: false, + }, + }, + application: {}, + }); + payload.params.index = ['some*', 'myfa*']; + await alert.executor(payload); + expect(ruleStatusService.error).toHaveBeenCalled(); + expect(ruleStatusService.error.mock.calls[0][0]).toContain( + 'The rule does not have read privileges to any of the following indices: ["myfa*","some*"]' + ); + }); + it('should NOT warn about the gap between runs if gap small', async () => { (getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(1, 'm')); (getGapMaxCatchupRatio as jest.Mock).mockReturnValue({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 476b9aa56f572..1b8fd580e1718 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -7,6 +7,7 @@ /* eslint-disable complexity */ import { Logger, KibanaRequest } from 'src/core/server'; +import { partition } from 'lodash'; import { SIGNALS_ID, @@ -41,6 +42,7 @@ import { createSearchAfterReturnType, mergeReturns, createSearchAfterReturnTypeFromResponse, + checkPrivileges, } from './utils'; import { signalParamsSchema } from './signal_params_schema'; import { siemRuleActionGroups } from './siem_rule_action_groups'; @@ -171,8 +173,51 @@ export const signalRulesAlertType = ({ logger.debug(buildRuleMessage('[+] Starting Signal Rule execution')); logger.debug(buildRuleMessage(`interval: ${interval}`)); + let wroteStatus = false; await ruleStatusService.goingToRun(); + // check if rule has permissions to access given index pattern + // move this collection of lines into a function in utils + // so that we can use it in create rules route, bulk, etc. + try { + const inputIndex = await getInputIndex(services, version, index); + const privileges = await checkPrivileges(services, inputIndex); + + const indexNames = Object.keys(privileges.index); + const [indexesWithReadPrivileges, indexesWithNoReadPrivileges] = partition( + indexNames, + (indexName) => privileges.index[indexName].read + ); + + if ( + indexesWithReadPrivileges.length > 0 && + indexesWithNoReadPrivileges.length >= indexesWithReadPrivileges.length + ) { + // some indices have read privileges others do not. + // set a partial failure status + const errorString = `Missing required read permissions on indexes: ${JSON.stringify( + indexesWithNoReadPrivileges + )}`; + logger.debug(buildRuleMessage(errorString)); + await ruleStatusService.partialFailure(errorString); + wroteStatus = true; + } else if ( + indexesWithReadPrivileges.length === 0 && + indexesWithNoReadPrivileges.length === indexNames.length + ) { + // none of the indices had read privileges so set the status to failed + // since we can't search on any indices we do not have read privileges on + const errorString = `The rule does not have read privileges to any of the following indices: ${JSON.stringify( + indexesWithNoReadPrivileges + )}`; + logger.debug(buildRuleMessage(errorString)); + await ruleStatusService.error(errorString); + wroteStatus = true; + } + } catch (exc) { + logger.error(buildRuleMessage(`Check privileges failed to execute ${exc}`)); + } + const gap = getGapBetweenRuns({ previousStartedAt, interval, from, to }); if (gap != null && gap.asMilliseconds() > 0) { const fromUnit = from[from.length - 1]; @@ -600,7 +645,7 @@ export const signalRulesAlertType = ({ `[+] Finished indexing ${result.createdSignalsCount} signals into ${outputIndex}` ) ); - if (!hasError) { + if (!hasError && !wroteStatus) { await ruleStatusService.success('succeeded', { bulkCreateTimeDurations: result.bulkCreateTimes, searchAfterTimeDurations: result.searchAfterTimes, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 18f6e8d127b1b..92a27319723d6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -52,6 +52,20 @@ export const shorthandMap = { }, }; +export const checkPrivileges = async (services: AlertServices, indices: string[]) => + services.callCluster('transport.request', { + path: '/_security/user/_has_privileges', + method: 'POST', + body: { + index: [ + { + names: indices ?? [], + privileges: ['read'], + }, + ], + }, + }); + export const getGapMaxCatchupRatio = ({ logger, previousStartedAt, From 39dc4d0237a6ab690d8d43d69f665b88d965e22e Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 22 Dec 2020 17:58:22 -0500 Subject: [PATCH 25/72] fix group edit popup format (#86836) --- .../group_selector/group_list/group_list.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_list/group_list.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_list/group_list.js index d989064c5057f..b17172aba6a95 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_list/group_list.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_list/group_list.js @@ -7,7 +7,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { EuiIcon, keys } from '@elastic/eui'; +import { EuiFlexItem, EuiFlexGroup, EuiIcon, keys } from '@elastic/eui'; import { JobGroup } from '../../../job_group'; @@ -97,8 +97,14 @@ export class GroupList extends Component { onClick={() => this.selectGroup(g)} ref={(ref) => this.setRef(ref, index)} > - - + + + + + + + +

    ))}
    From f2105c85fbcc4c311ae105440338c693cb18bd86 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Tue, 22 Dec 2020 16:27:05 -0700 Subject: [PATCH 26/72] Sample data usage collector es client migration (#86657) * Uses new esClient to fetch sample data telemetry * Import SearchResponse from core --- .../sample_data/usage/collector_fetch.test.ts | 2 +- .../sample_data/usage/collector_fetch.ts | 25 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/plugins/home/server/services/sample_data/usage/collector_fetch.test.ts b/src/plugins/home/server/services/sample_data/usage/collector_fetch.test.ts index 54fed3db1de4d..58bb037f8d614 100644 --- a/src/plugins/home/server/services/sample_data/usage/collector_fetch.test.ts +++ b/src/plugins/home/server/services/sample_data/usage/collector_fetch.test.ts @@ -23,7 +23,7 @@ import { fetchProvider } from './collector_fetch'; const getMockFetchClients = (hits?: unknown[]) => { const fetchParamsMock = createCollectorFetchContextMock(); - fetchParamsMock.callCluster.mockResolvedValue({ hits: { hits } }); + fetchParamsMock.esClient.search = jest.fn().mockResolvedValue({ body: { hits: { hits } } }); return fetchParamsMock; }; diff --git a/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts b/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts index 7df9b14d2efb1..ef958873d9663 100644 --- a/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts +++ b/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts @@ -19,6 +19,7 @@ import { get } from 'lodash'; import moment from 'moment'; +import { SearchResponse } from 'src/core/server'; import { CollectorFetchContext } from '../../../../../usage_collection/server'; interface SearchHit { @@ -41,17 +42,23 @@ export interface TelemetryResponse { last_uninstall_set: string | null; } +type ESResponse = SearchResponse; + export function fetchProvider(index: string) { - return async ({ callCluster }: CollectorFetchContext) => { - const response = await callCluster('search', { - index, - body: { - query: { term: { type: { value: 'sample-data-telemetry' } } }, - _source: { includes: ['sample-data-telemetry', 'type', 'updated_at'] }, + return async ({ esClient }: CollectorFetchContext) => { + const { body: response } = await esClient.search( + { + index, + body: { + query: { term: { type: { value: 'sample-data-telemetry' } } }, + _source: { includes: ['sample-data-telemetry', 'type', 'updated_at'] }, + }, + filter_path: 'hits.hits._id,hits.hits._source', }, - filter_path: 'hits.hits._id,hits.hits._source', - ignore: [404], - }); + { + ignore: [404], + } + ); const getLast = ( dataSet: string, From c9879c6fbdf3fc919be49497faa20d56c13115d0 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 22 Dec 2020 16:31:14 -0800 Subject: [PATCH 27/72] Skip test preventing ES snapshot promotion Signed-off-by: Tyler Smalley --- .../security_and_spaces/tests/generating_signals.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index 64ee42fdb3f3e..e3264786ff38b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -201,7 +201,8 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('EQL Rules', () => { + // ES PROMOTION FAILURE: http://github.com/elastic/kibana/issues/86709 + describe.skip('EQL Rules', () => { it('generates signals from EQL sequences in the expected form', async () => { const rule: EqlCreateSchema = { ...getRuleForSignalTesting(['auditbeat-*']), From 62b5ef9459797724c54f54ced8469e708423fa1a Mon Sep 17 00:00:00 2001 From: Constance Date: Tue, 22 Dec 2020 17:52:32 -0800 Subject: [PATCH 28/72] Minor responsive text overflow tweaks to ApiCodeExample POST (#86848) --- .../creation_mode_components/api_code_example.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx index 9ebe404659ca2..c33cda9f7e429 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx @@ -23,6 +23,8 @@ import { EuiBadge, EuiCode, EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url'; @@ -95,8 +97,14 @@ export const FlyoutBody: React.FC = () => { - POST - {documentsApiUrl} + + + POST + + + {documentsApiUrl} + + {dedent(` From e3896050fc022b2ac981e48e8e63e427ba3a2e6e Mon Sep 17 00:00:00 2001 From: Constance Date: Tue, 22 Dec 2020 17:53:07 -0800 Subject: [PATCH 29/72] [App Search] Misc credentials key UI enhancements (#81817) * Fix screen reader still reading out bullet bullet bullet * Even out horizontal spacing * Break long password text * Make EUI table cell full width on mobile --- .../credentials/credentials_list/credentials_list.test.tsx | 6 +++++- .../credentials/credentials_list/credentials_list.tsx | 5 +++++ .../components/credentials/credentials_list/key.tsx | 1 + .../public/applications/shared/hidden_text/hidden_text.tsx | 4 +++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx index 4f5ded0a3ccc1..ec018f0faf5ff 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx @@ -204,7 +204,11 @@ describe('Credentials', () => { copy: expect.any(Function), toggleIsHidden: expect.any(Function), isHidden: expect.any(Boolean), - text: •••••••, + text: ( + + ••••••• + + ), }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx index 9240bade4975e..df85a9c3053a6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx @@ -40,6 +40,7 @@ export const CredentialsList: React.FC = () => { { name: 'Key', width: '36%', + className: 'eui-textBreakAll', render: (token: ApiToken) => { const { key } = token; if (!key) return null; @@ -60,6 +61,10 @@ export const CredentialsList: React.FC = () => { ); }, + mobileOptions: { + // @ts-ignore - EUI's type definitions need to be updated + width: '100%', + }, }, { name: 'Modes', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx index fa2d124cbccdf..8ea2b6c284fc6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx @@ -39,6 +39,7 @@ export const Key: React.FC = ({ copy, toggleIsHidden, isHidden, text }) = iconType={hideIcon} aria-label={hideIconLabel} aria-pressed={!isHidden} + style={{ marginRight: '0.25em' }} /> {text} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx index 69176b8a139e7..dae22a47035c4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx @@ -25,7 +25,9 @@ export const HiddenText: React.FC = ({ text, children }) => { defaultMessage: 'Hidden text', }); const hiddenText = isHidden ? ( - {text.replace(/./g, '•')} + + {text.replace(/./g, '•')} + ) : ( text ); From 35b10b53549c954a5df2ed3446b84418eae5b45b Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Tue, 22 Dec 2020 21:14:22 -0500 Subject: [PATCH 30/72] [Security Solution] [Detections] Bug fix for read privilege check during rule exec (#86852) --- .../detection_engine/signals/signal_rule_alert_type.test.ts | 5 ++++- .../lib/detection_engine/signals/signal_rule_alert_type.ts | 5 +---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 0ba2507e2e649..f8983061d7a7a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -197,13 +197,16 @@ describe('rules_notification_alert_type', () => { 'myfa*': { read: true, }, + 'anotherindex*': { + read: true, + }, 'some*': { read: false, }, }, application: {}, }); - payload.params.index = ['some*', 'myfa*']; + payload.params.index = ['some*', 'myfa*', 'anotherindex*']; await alert.executor(payload); expect(ruleStatusService.partialFailure).toHaveBeenCalled(); expect(ruleStatusService.partialFailure.mock.calls[0][0]).toContain( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 1b8fd580e1718..8a219d926a96d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -189,10 +189,7 @@ export const signalRulesAlertType = ({ (indexName) => privileges.index[indexName].read ); - if ( - indexesWithReadPrivileges.length > 0 && - indexesWithNoReadPrivileges.length >= indexesWithReadPrivileges.length - ) { + if (indexesWithReadPrivileges.length > 0 && indexesWithNoReadPrivileges.length > 0) { // some indices have read privileges others do not. // set a partial failure status const errorString = `Missing required read permissions on indexes: ${JSON.stringify( From 3dfb1aba2a652b5f1464604e0e59302d9fa1d122 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Wed, 23 Dec 2020 00:27:37 -0500 Subject: [PATCH 31/72] [Security Solution][Detections] - Fix export on exceptions list view (#86135) ## Summary This PR addresses a fix on the exceptions list table export functionality. A dedicated route for exception list export needed to be created. List is exported into an `.ndjson` format. Exception lists consist of two elements - the list itself, and its items. The export file should now contain both these elements, the list followed by its items. --- ...export_exception_list_query_schema.mock.ts | 15 +++ ...export_exception_list_query_schema.test.ts | 80 ++++++++++++++ .../export_exception_list_query_schema.ts | 20 ++++ .../lists/common/schemas/request/index.ts | 1 + .../lists/public/exceptions/api.test.ts | 47 ++++++++ x-pack/plugins/lists/public/exceptions/api.ts | 25 +++++ .../lists/public/exceptions/hooks/use_api.ts | 25 ++++- .../plugins/lists/public/exceptions/types.ts | 19 ++++ .../routes/export_exception_list_route.ts | 103 ++++++++++++++++++ x-pack/plugins/lists/server/routes/index.ts | 1 + .../lists/server/routes/init_routes.ts | 2 + .../auto_download}/auto_download.test.tsx | 0 .../auto_download}/auto_download.tsx | 0 .../value_lists_management_modal/modal.tsx | 2 +- .../rules/all/exceptions/columns.tsx | 23 +++- .../rules/all/exceptions/exceptions_table.tsx | 94 +++++++++++++--- .../rules/all/exceptions/translations.ts | 23 +++- .../exceptions/use_all_exception_lists.tsx | 2 +- 18 files changed, 459 insertions(+), 23 deletions(-) create mode 100644 x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.mock.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.test.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.ts create mode 100644 x-pack/plugins/lists/server/routes/export_exception_list_route.ts rename x-pack/plugins/security_solution/public/{detections/components/value_lists_management_modal => common/components/auto_download}/auto_download.test.tsx (100%) rename x-pack/plugins/security_solution/public/{detections/components/value_lists_management_modal => common/components/auto_download}/auto_download.tsx (100%) diff --git a/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.mock.ts new file mode 100644 index 0000000000000..4e6655ec1d1d6 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.mock.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ID, LIST_ID, NAMESPACE_TYPE } from '../../constants.mock'; + +import { ExportExceptionListQuerySchema } from './export_exception_list_query_schema'; + +export const getExportExceptionListQuerySchemaMock = (): ExportExceptionListQuerySchema => ({ + id: ID, + list_id: LIST_ID, + namespace_type: NAMESPACE_TYPE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.test.ts new file mode 100644 index 0000000000000..6af7f6323c135 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; + +import { + ExportExceptionListQuerySchema, + exportExceptionListQuerySchema, +} from './export_exception_list_query_schema'; +import { getExportExceptionListQuerySchemaMock } from './export_exception_list_query_schema.mock'; + +describe('export_exception_list_schema', () => { + test('it should validate a typical lists request', () => { + const payload = getExportExceptionListQuerySchemaMock(); + const decoded = exportExceptionListQuerySchema.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 accept an undefined for an id', () => { + const payload = getExportExceptionListQuerySchemaMock(); + // @ts-expect-error + delete payload.id; + const decoded = exportExceptionListQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "undefined" supplied to "id"']); + expect(message.schema).toEqual({}); + }); + + test('it should default namespace_type to "single" if an undefined given for namespacetype', () => { + const payload = getExportExceptionListQuerySchemaMock(); + delete payload.namespace_type; + const decoded = exportExceptionListQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(message.schema).toEqual({ + id: 'uuid_here', + list_id: 'some-list-id', + namespace_type: 'single', + }); + }); + + test('it should NOT accept an undefined for an list_id', () => { + const payload = getExportExceptionListQuerySchemaMock(); + // @ts-expect-error + delete payload.list_id; + const decoded = exportExceptionListQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "list_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: ExportExceptionListQuerySchema & { + extraKey?: string; + } = getExportExceptionListQuerySchemaMock(); + payload.extraKey = 'some new value'; + const decoded = exportExceptionListQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.ts b/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.ts new file mode 100644 index 0000000000000..b5061e903a824 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; + +import { id, list_id, namespace_type } from '../common/schemas'; + +export const exportExceptionListQuerySchema = t.exact( + t.type({ + id, + list_id, + namespace_type, + // TODO: Add file_name here with a default value + }) +); + +export type ExportExceptionListQuerySchema = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/request/index.ts b/x-pack/plugins/lists/common/schemas/request/index.ts index 172d73a5c7377..36e41bf52aa40 100644 --- a/x-pack/plugins/lists/common/schemas/request/index.ts +++ b/x-pack/plugins/lists/common/schemas/request/index.ts @@ -14,6 +14,7 @@ export * from './delete_exception_list_item_schema'; export * from './delete_exception_list_schema'; export * from './delete_list_item_schema'; export * from './delete_list_schema'; +export * from './export_exception_list_query_schema'; export * from './export_list_item_query_schema'; export * from './find_endpoint_list_item_schema'; export * from './find_exception_list_item_schema'; diff --git a/x-pack/plugins/lists/public/exceptions/api.test.ts b/x-pack/plugins/lists/public/exceptions/api.test.ts index e45403e319c29..7570e1f050abb 100644 --- a/x-pack/plugins/lists/public/exceptions/api.test.ts +++ b/x-pack/plugins/lists/public/exceptions/api.test.ts @@ -25,6 +25,7 @@ import { addExceptionListItem, deleteExceptionListById, deleteExceptionListItemById, + exportExceptionList, fetchExceptionListById, fetchExceptionListItemById, fetchExceptionLists, @@ -870,4 +871,50 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse).toEqual({}); }); }); + + describe('#exportExceptionList', () => { + const blob: Blob = { + arrayBuffer: jest.fn(), + size: 89, + slice: jest.fn(), + stream: jest.fn(), + text: jest.fn(), + type: 'json', + } as Blob; + + beforeEach(() => { + httpMock.fetch.mockResolvedValue(blob); + }); + + test('it invokes "exportExceptionList" with expected url and body values', async () => { + await exportExceptionList({ + http: httpMock, + id: 'some-id', + listId: 'list-id', + namespaceType: 'single', + signal: abortCtrl.signal, + }); + + expect(httpMock.fetch).toHaveBeenCalledWith('/api/exception_lists/_export', { + method: 'GET', + query: { + id: 'some-id', + list_id: 'list-id', + namespace_type: 'single', + }, + signal: abortCtrl.signal, + }); + }); + + test('it returns expected list to export on success', async () => { + const exceptionResponse = await exportExceptionList({ + http: httpMock, + id: 'some-id', + listId: 'list-id', + namespaceType: 'single', + signal: abortCtrl.signal, + }); + expect(exceptionResponse).toEqual(blob); + }); + }); }); diff --git a/x-pack/plugins/lists/public/exceptions/api.ts b/x-pack/plugins/lists/public/exceptions/api.ts index fc0c8934d6397..f7032c22cb6c2 100644 --- a/x-pack/plugins/lists/public/exceptions/api.ts +++ b/x-pack/plugins/lists/public/exceptions/api.ts @@ -41,6 +41,7 @@ import { ApiCallByIdProps, ApiCallByListIdProps, ApiCallFetchExceptionListsProps, + ExportExceptionListProps, UpdateExceptionListItemProps, UpdateExceptionListProps, } from './types'; @@ -537,3 +538,27 @@ export const addEndpointExceptionList = async ({ return Promise.reject(error); } }; + +/** + * Fetch an ExceptionList by providing a ExceptionList ID + * + * @param http Kibana http service + * @param id ExceptionList ID (not list_id) + * @param listId ExceptionList LIST_ID (not id) + * @param namespaceType ExceptionList namespace_type + * @param signal to cancel request + * + * @throws An error if response is not OK + */ +export const exportExceptionList = async ({ + http, + id, + listId, + namespaceType, + signal, +}: ExportExceptionListProps): Promise => + http.fetch(`${EXCEPTION_LIST_URL}/_export`, { + method: 'GET', + query: { id, list_id: listId, namespace_type: namespaceType }, + signal, + }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_api.ts b/x-pack/plugins/lists/public/exceptions/hooks/use_api.ts index def2f2626b8ec..31a8d3ac5f598 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_api.ts +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_api.ts @@ -9,7 +9,7 @@ import { useMemo } from 'react'; import * as Api from '../api'; import { HttpStart } from '../../../../../../src/core/public'; import { ExceptionListItemSchema, ExceptionListSchema } from '../../../common/schemas'; -import { ApiCallFindListsItemsMemoProps, ApiCallMemoProps } from '../types'; +import { ApiCallFindListsItemsMemoProps, ApiCallMemoProps, ApiListExportProps } from '../types'; import { getIdsAndNamespaces } from '../utils'; export interface ExceptionsApi { @@ -22,6 +22,7 @@ export interface ExceptionsApi { arg: ApiCallMemoProps & { onSuccess: (arg: ExceptionListSchema) => void } ) => Promise; getExceptionListsItems: (arg: ApiCallFindListsItemsMemoProps) => Promise; + exportExceptionList: (arg: ApiListExportProps) => Promise; } export const useApi = (http: HttpStart): ExceptionsApi => { @@ -67,6 +68,28 @@ export const useApi = (http: HttpStart): ExceptionsApi => { onError(error); } }, + async exportExceptionList({ + id, + listId, + namespaceType, + onError, + onSuccess, + }: ApiListExportProps): Promise { + const abortCtrl = new AbortController(); + + try { + const blob = await Api.exportExceptionList({ + http, + id, + listId, + namespaceType, + signal: abortCtrl.signal, + }); + onSuccess(blob); + } catch (error) { + onError(error); + } + }, async getExceptionItem({ id, namespaceType, diff --git a/x-pack/plugins/lists/public/exceptions/types.ts b/x-pack/plugins/lists/public/exceptions/types.ts index 02b78bc1a5e58..6a238e22344b6 100644 --- a/x-pack/plugins/lists/public/exceptions/types.ts +++ b/x-pack/plugins/lists/public/exceptions/types.ts @@ -90,6 +90,17 @@ export interface ApiCallMemoProps { onSuccess: () => void; } +// TODO: Switch to use ApiCallMemoProps +// after cleaning up exceptions/api file to +// remove unnecessary validation checks +export interface ApiListExportProps { + id: string; + listId: string; + namespaceType: NamespaceType; + onError: (err: Error) => void; + onSuccess: (blob: Blob) => void; +} + export interface ApiCallFindListsItemsMemoProps { lists: ExceptionListIdentifiers[]; filterOptions: FilterExceptionsOptions[]; @@ -156,3 +167,11 @@ export interface AddEndpointExceptionListProps { http: HttpStart; signal: AbortSignal; } + +export interface ExportExceptionListProps { + http: HttpStart; + id: string; + listId: string; + namespaceType: NamespaceType; + signal: AbortSignal; +} diff --git a/x-pack/plugins/lists/server/routes/export_exception_list_route.ts b/x-pack/plugins/lists/server/routes/export_exception_list_route.ts new file mode 100644 index 0000000000000..1394bf48cd2c7 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/export_exception_list_route.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { exportExceptionListQuerySchema } from '../../common/schemas'; + +import { getExceptionListClient } from './utils'; + +export const exportExceptionListRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists-read'], + }, + path: `${EXCEPTION_LIST_URL}/_export`, + validate: { + query: buildRouteValidation(exportExceptionListQuerySchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { id, list_id: listId, namespace_type: namespaceType } = request.query; + const exceptionLists = getExceptionListClient(context); + const exceptionList = await exceptionLists.getExceptionList({ + id, + listId, + namespaceType, + }); + + if (exceptionList == null) { + return siemResponse.error({ + body: `list_id: ${listId} does not exist`, + statusCode: 400, + }); + } else { + const { exportData: exportList } = getExport([exceptionList]); + const listItems = await exceptionLists.findExceptionListItem({ + filter: undefined, + listId, + namespaceType, + page: 1, + perPage: 10000, + sortField: 'exception-list.created_at', + sortOrder: 'desc', + }); + + const { exportData: exportListItems, exportDetails } = getExport(listItems?.data ?? []); + + const responseBody = [ + exportList, + exportListItems, + { exception_list_items_details: exportDetails }, + ]; + + // TODO: Allow the API to override the name of the file to export + const fileName = exceptionList.list_id; + return response.ok({ + body: transformDataToNdjson(responseBody), + headers: { + 'Content-Disposition': `attachment; filename="${fileName}"`, + 'Content-Type': 'application/ndjson', + }, + }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; + +const transformDataToNdjson = (data: unknown[]): string => { + if (data.length !== 0) { + const dataString = data.map((dataItem) => JSON.stringify(dataItem)).join('\n'); + return `${dataString}\n`; + } else { + return ''; + } +}; + +export const getExport = ( + data: unknown[] +): { + exportData: string; + exportDetails: string; +} => { + const ndjson = transformDataToNdjson(data); + const exportDetails = JSON.stringify({ + exported_count: data.length, + }); + return { exportData: ndjson, exportDetails: `${exportDetails}\n` }; +}; diff --git a/x-pack/plugins/lists/server/routes/index.ts b/x-pack/plugins/lists/server/routes/index.ts index 0d99d726d232d..a1a54a88c0ed0 100644 --- a/x-pack/plugins/lists/server/routes/index.ts +++ b/x-pack/plugins/lists/server/routes/index.ts @@ -17,6 +17,7 @@ export * from './delete_exception_list_item_route'; export * from './delete_list_index_route'; export * from './delete_list_item_route'; export * from './delete_list_route'; +export * from './export_exception_list_route'; export * from './export_list_item_route'; export * from './find_endpoint_list_item_route'; export * from './find_exception_list_item_route'; diff --git a/x-pack/plugins/lists/server/routes/init_routes.ts b/x-pack/plugins/lists/server/routes/init_routes.ts index 163126f1277c1..1f29d0aaeeb48 100644 --- a/x-pack/plugins/lists/server/routes/init_routes.ts +++ b/x-pack/plugins/lists/server/routes/init_routes.ts @@ -22,6 +22,7 @@ import { deleteListIndexRoute, deleteListItemRoute, deleteListRoute, + exportExceptionListRoute, exportListItemRoute, findEndpointListItemRoute, findExceptionListItemRoute, @@ -76,6 +77,7 @@ export const initRoutes = (router: IRouter, config: ConfigType): void => { updateExceptionListRoute(router); deleteExceptionListRoute(router); findExceptionListRoute(router); + exportExceptionListRoute(router); // exception list items createExceptionListItemRoute(router); diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/auto_download.test.tsx b/x-pack/plugins/security_solution/public/common/components/auto_download/auto_download.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/auto_download.test.tsx rename to x-pack/plugins/security_solution/public/common/components/auto_download/auto_download.test.tsx diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/auto_download.tsx b/x-pack/plugins/security_solution/public/common/components/auto_download/auto_download.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/auto_download.tsx rename to x-pack/plugins/security_solution/public/common/components/auto_download/auto_download.tsx diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx index f0e47fcd5c104..57c4eee95cd8c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx @@ -32,8 +32,8 @@ import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import * as i18n from './translations'; import { buildColumns } from './table_helpers'; import { ValueListsForm } from './form'; -import { AutoDownload } from './auto_download'; import { ReferenceErrorModal } from './reference_error_modal'; +import { AutoDownload } from '../../../common/components/auto_download/auto_download'; interface ValueListsModalProps { onClose: () => void; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx index 57b86119dc164..79cfd53a4fa00 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiButtonIcon, EuiBasicTableColumn, EuiToolTip } from '@elastic/eui'; import { History } from 'history'; +import { NamespaceType } from '../../../../../../../../lists/common'; import { FormatUrl } from '../../../../../../common/components/link_to'; import { LinkAnchor } from '../../../../../../common/components/links'; import * as i18n from './translations'; @@ -16,7 +17,11 @@ import { ExceptionListInfo } from './use_all_exception_lists'; import { getRuleDetailsUrl } from '../../../../../../common/components/link_to/redirect_to_detection_engine'; export type AllExceptionListsColumns = EuiBasicTableColumn; -export type Func = (listId: string) => () => void; +export type Func = (arg: { + id: string; + listId: string; + namespaceType: NamespaceType; +}) => () => void; export const getAllExceptionListsColumns = ( onExport: Func, @@ -96,9 +101,13 @@ export const getAllExceptionListsColumns = ( align: 'center', isExpander: false, width: '25px', - render: (list: ExceptionListInfo) => ( + render: ({ id, list_id: listId, namespace_type: namespaceType }: ExceptionListInfo) => ( @@ -108,10 +117,14 @@ export const getAllExceptionListsColumns = ( align: 'center', width: '25px', isExpander: false, - render: (list: ExceptionListInfo) => ( + render: ({ id, list_id: listId, namespace_type: namespaceType }: ExceptionListInfo) => ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx index 65aaaea06b40f..ac9c558022c26 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, useEffect, useCallback, useState, ChangeEvent } from 'react'; +import React, { useMemo, useEffect, useCallback, useState } from 'react'; import { EuiBasicTable, EuiEmptyPrompt, @@ -16,8 +16,10 @@ import styled from 'styled-components'; import { History } from 'history'; import { set } from 'lodash/fp'; +import { AutoDownload } from '../../../../../../common/components/auto_download/auto_download'; +import { NamespaceType } from '../../../../../../../../lists/common'; import { useKibana } from '../../../../../../common/lib/kibana'; -import { useExceptionLists } from '../../../../../../shared_imports'; +import { useApi, useExceptionLists } from '../../../../../../shared_imports'; import { FormatUrl } from '../../../../../../common/components/link_to'; import { HeaderSection } from '../../../../../../common/components/header_section'; import { Loader } from '../../../../../../common/components/loader'; @@ -51,6 +53,7 @@ export const ExceptionListsTable = React.memo( const { services: { http, notifications }, } = useKibana(); + const { exportExceptionList } = useApi(http); const [filters, setFilters] = useState({ name: null, list_id: null, @@ -69,10 +72,67 @@ export const ExceptionListsTable = React.memo( }); const [initLoading, setInitLoading] = useState(true); const [lastUpdated, setLastUpdated] = useState(Date.now()); + const [deletingListIds, setDeletingListIds] = useState([]); + const [exportingListIds, setExportingListIds] = useState([]); + const [exportDownload, setExportDownload] = useState<{ name?: string; blob?: Blob }>({}); - const handleDelete = useCallback((id: string) => () => {}, []); + const handleDelete = useCallback( + ({ + id, + listId, + namespaceType, + }: { + id: string; + listId: string; + namespaceType: NamespaceType; + }) => async () => { + try { + setDeletingListIds((ids) => [...ids, id]); + // route to patch rules with associated exception list + } catch (error) { + notifications.toasts.addError(error, { title: i18n.EXCEPTION_DELETE_ERROR }); + } finally { + setDeletingListIds((ids) => [...ids.filter((_id) => _id !== id)]); + } + }, + [notifications.toasts] + ); - const handleExport = useCallback((id: string) => () => {}, []); + const handleExportSuccess = useCallback( + (listId: string) => (blob: Blob): void => { + setExportDownload({ name: listId, blob }); + }, + [] + ); + + const handleExportError = useCallback( + (err: Error) => { + notifications.toasts.addError(err, { title: i18n.EXCEPTION_EXPORT_ERROR }); + }, + [notifications.toasts] + ); + + const handleExport = useCallback( + ({ + id, + listId, + namespaceType, + }: { + id: string; + listId: string; + namespaceType: NamespaceType; + }) => async () => { + setExportingListIds((ids) => [...ids, id]); + await exportExceptionList({ + id, + listId, + namespaceType, + onError: handleExportError, + onSuccess: handleExportSuccess(listId), + }); + }, + [exportExceptionList, handleExportError, handleExportSuccess] + ); const exceptionsColumns = useMemo((): AllExceptionListsColumns[] => { return getAllExceptionListsColumns(handleExport, handleDelete, history, formatUrl); @@ -122,14 +182,6 @@ export const ExceptionListsTable = React.memo( setFilters(formattedFilter); }, []); - const handleSearchChange = useCallback( - (event: ChangeEvent) => { - const val = event.target.value; - handleSearch(val); - }, - [handleSearch] - ); - const paginationMemo = useMemo( () => ({ pageIndex: pagination.page - 1, @@ -140,8 +192,23 @@ export const ExceptionListsTable = React.memo( [pagination] ); + const handleOnDownload = useCallback(() => { + setExportDownload({}); + }, []); + + const tableItems = (data ?? []).map((item) => ({ + ...item, + isDeleting: deletingListIds.includes(item.id), + isExporting: exportingListIds.includes(item.id), + })); + return ( <> + <> {loadingTableInfo && ( @@ -162,7 +229,6 @@ export const ExceptionListsTable = React.memo( aria-label={i18n.EXCEPTIONS_LISTS_SEARCH_PLACEHOLDER} placeholder={i18n.EXCEPTIONS_LISTS_SEARCH_PLACEHOLDER} onSearch={handleSearch} - onChange={handleSearchChange} disabled={initLoading} incremental={false} fullWidth @@ -188,7 +254,7 @@ export const ExceptionListsTable = React.memo( columns={exceptionsColumns} isSelectable={!hasNoPermissions ?? false} itemId="id" - items={data ?? []} + items={tableItems} noItemsMessage={emptyPrompt} onChange={() => {}} pagination={paginationMemo} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/translations.ts index 2eba8fb2e579b..7483b8e943d30 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/translations.ts @@ -35,7 +35,7 @@ export const LIST_DATE_CREATED_TITLE = i18n.translate( ); export const LIST_DATE_UPDATED_TITLE = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.all.exceptions.dateUPdatedTitle', + 'xpack.securitySolution.detectionEngine.rules.all.exceptions.dateUpdatedTitle', { defaultMessage: 'Last edited', } @@ -75,3 +75,24 @@ export const NO_LISTS_BODY = i18n.translate( defaultMessage: "We weren't able to find any exception lists.", } ); + +export const EXCEPTION_EXPORT_SUCCESS = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.all.exceptions.exportSuccess', + { + defaultMessage: 'Exception list export success', + } +); + +export const EXCEPTION_EXPORT_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.all.exceptions.exportError', + { + defaultMessage: 'Exception list export error', + } +); + +export const EXCEPTION_DELETE_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.all.exceptions.deleteError', + { + defaultMessage: 'Error occurred deleting exception list', + } +); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/use_all_exception_lists.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/use_all_exception_lists.tsx index 4b47080cc2da1..3f343da605213 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/use_all_exception_lists.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/use_all_exception_lists.tsx @@ -61,7 +61,7 @@ export const useAllExceptionLists = ({ const { data: rules } = await fetchRules({ pagination: { page: 1, - perPage: 500, + perPage: 10000, total: 0, }, signal: abortCtrl.signal, From 4f2fd01661a5095ce7b4db33909b6d1f6addc572 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 22 Dec 2020 23:05:42 -0800 Subject: [PATCH 32/72] [CI] Updates branch env variables (#86712) Signed-off-by: Tyler Smalley --- packages/kbn-apm-config-loader/src/config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kbn-apm-config-loader/src/config.ts b/packages/kbn-apm-config-loader/src/config.ts index 6e5a830d04b17..5e3d52cfd27d1 100644 --- a/packages/kbn-apm-config-loader/src/config.ts +++ b/packages/kbn-apm-config-loader/src/config.ts @@ -153,8 +153,8 @@ export class ApmConfiguration { return { globalLabels: { - branch: process.env.ghprbSourceBranch || '', - targetBranch: process.env.ghprbTargetBranch || '', + branch: process.env.GIT_BRANCH || '', + targetBranch: process.env.PR_TARGET_BRANCH || '', ciBuildNumber: process.env.BUILD_NUMBER || '', isPr: process.env.GITHUB_PR_NUMBER ? true : false, prId: process.env.GITHUB_PR_NUMBER || '', From 21b34bcd242daeb402a0a43c51f821e0d8989987 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Wed, 23 Dec 2020 09:36:38 +0200 Subject: [PATCH 33/72] [Security Solution][Case] Fix case status dropdown on modals (#86243) --- .../all_cases/table_filters.test.tsx | 18 +++++++- .../components/all_cases/table_filters.tsx | 46 ++++++++++++------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx index 0c9a725f918e5..a92fc793c796e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx @@ -8,11 +8,11 @@ import React from 'react'; import { mount } from 'enzyme'; import { CaseStatuses } from '../../../../../case/common/api'; -import { CasesTableFilters } from './table_filters'; import { TestProviders } from '../../../common/mock'; import { useGetTags } from '../../containers/use_get_tags'; import { useGetReporters } from '../../containers/use_get_reporters'; import { DEFAULT_FILTER_OPTIONS } from '../../containers/use_get_cases'; +import { CasesTableFilters } from './table_filters'; jest.mock('../../containers/use_get_reporters'); jest.mock('../../containers/use_get_tags'); @@ -151,4 +151,20 @@ describe('CasesTableFilters ', () => { ); expect(onFilterChanged).toHaveBeenCalledWith({ reporters: [{ username: 'casetester' }] }); }); + + it('StatusFilterWrapper should have a fixed width of 180px', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="status-filter-wrapper"]').first()).toHaveStyleRule( + 'flex-basis', + '180px', + { + modifier: '&&', + } + ); + }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx index f5ec0bf144154..768ad300c02e6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx @@ -6,6 +6,7 @@ import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { isEqual } from 'lodash/fp'; +import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiFilterGroup } from '@elastic/eui'; import { CaseStatuses } from '../../../../../case/common/api'; @@ -25,6 +26,13 @@ interface CasesTableFiltersProps { setFilterRefetch: (val: () => void) => void; } +// Fix the width of the status dropdown to prevent hiding long text items +const StatusFilterWrapper = styled(EuiFlexItem)` + && { + flex-basis: 180px; + } +`; + /** * Collection of filters for filtering data within the CasesTable. Contains search bar, * and tag selection @@ -131,23 +139,27 @@ const CasesTableFiltersComponent = ({ ); return ( - - - - - - + + + + + + + + + + From 4613da5c27dd6a2c4a5bb13d17a4ac50c827204e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 23 Dec 2020 09:02:15 +0100 Subject: [PATCH 34/72] [APM] Truncate long service names in Trace overview (#86759) * truncating service name * truncating service name --- .../apm/public/components/app/TraceOverview/TraceList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx index e68f8a9809bf5..eebd03772f238 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx @@ -44,7 +44,7 @@ const traceListColumns: Array> = [ _: string, { serviceName, transactionName, transactionType }: TraceGroup ) => ( - + Date: Wed, 23 Dec 2020 09:05:29 +0100 Subject: [PATCH 35/72] [Discover] Integration of EuiDataGrid (#67259) Co-authored-by: Michail Yasonik Co-authored-by: Marta Bondyra Co-authored-by: Dave Snider Co-authored-by: Andrea Del Rio Co-authored-by: cchaos --- docs/management/advanced-options.asciidoc | 4 + src/core/public/chrome/ui/header/_index.scss | 14 + src/plugins/discover/common/index.ts | 1 + .../public/__mocks__/index_pattern.ts | 18 +- .../public/application/angular/discover.js | 28 +- .../angular/discover_datagrid.html | 31 ++ .../application/angular/discover_legacy.html | 3 +- .../application/angular/discover_state.ts | 5 + .../components/create_discover_directive.ts | 52 +++ .../create_discover_grid_directive.tsx | 56 +++ .../create_discover_legacy_directive.ts | 2 +- .../application/components/discover.scss | 12 + .../application/components/discover.tsx | 321 +++++++++++++++++ .../components/discover_grid/constants.ts | 38 ++ .../discover_grid/discover_grid.scss | 68 ++++ .../discover_grid/discover_grid.tsx | 336 ++++++++++++++++++ .../discover_grid_cell_actions.test.tsx | 80 +++++ .../discover_grid_cell_actions.tsx | 97 +++++ .../discover_grid_columns.test.tsx | 154 ++++++++ .../discover_grid/discover_grid_columns.tsx | 122 +++++++ .../discover_grid/discover_grid_context.tsx | 34 ++ .../discover_grid_expand_button.test.tsx | 106 ++++++ .../discover_grid_expand_button.tsx | 62 ++++ .../discover_grid/discover_grid_flyout.tsx | 143 ++++++++ .../discover_grid/discover_grid_schema.tsx | 103 ++++++ .../get_render_cell_value.test.tsx | 132 +++++++ .../discover_grid/get_render_cell_value.tsx | 116 ++++++ .../components/discover_grid/types.ts | 29 ++ .../components/discover_legacy.test.tsx | 2 +- .../components/discover_legacy.tsx | 127 ++++++- .../__snapshots__/doc_viewer.test.tsx.snap | 1 + .../components/doc_viewer/doc_viewer.scss | 1 - .../components/doc_viewer/doc_viewer.tsx | 2 +- .../__snapshots__/field_name.test.tsx.snap | 2 +- .../components/field_name/field_name.test.tsx | 2 +- .../sidebar/discover_field.test.tsx | 1 - .../sidebar/lib/group_fields.test.ts | 117 +++--- .../components/sidebar/lib/group_fields.tsx | 9 + .../embeddable/search_embeddable.ts | 35 +- .../embeddable/search_embeddable_factory.ts | 1 + .../embeddable/search_template.html | 16 +- .../embeddable/search_template_datagrid.html | 19 + .../helpers/get_sharing_data.test.ts | 6 +- .../helpers/persist_saved_search.ts | 3 + .../discover/public/get_inner_angular.ts | 7 +- .../public/saved_searches/_saved_search.ts | 2 + .../discover/public/saved_searches/types.ts | 2 + .../discover/server/saved_objects/search.ts | 1 + src/plugins/discover/server/ui_settings.ts | 18 + .../apps/dashboard/embeddable_data_grid.ts | 60 ++++ test/functional/apps/dashboard/index.ts | 1 + test/functional/apps/discover/_data_grid.ts | 67 ++++ .../apps/discover/_data_grid_context.ts | 91 +++++ .../discover/_data_grid_doc_navigation.ts | 91 +++++ .../apps/discover/_data_grid_doc_table.ts | 132 +++++++ .../apps/discover/_data_grid_field_data.ts | 99 ++++++ test/functional/apps/discover/_doc_table.ts | 15 +- test/functional/apps/discover/index.ts | 5 + test/functional/services/data_grid.ts | 138 ++++++- 59 files changed, 3156 insertions(+), 84 deletions(-) create mode 100644 src/plugins/discover/public/application/angular/discover_datagrid.html create mode 100644 src/plugins/discover/public/application/components/create_discover_directive.ts create mode 100644 src/plugins/discover/public/application/components/create_discover_grid_directive.tsx create mode 100644 src/plugins/discover/public/application/components/discover.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/constants.ts create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid.scss create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.test.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_context.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_schema.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/types.ts create mode 100644 src/plugins/discover/public/application/embeddable/search_template_datagrid.html create mode 100644 test/functional/apps/dashboard/embeddable_data_grid.ts create mode 100644 test/functional/apps/discover/_data_grid.ts create mode 100644 test/functional/apps/discover/_data_grid_context.ts create mode 100644 test/functional/apps/discover/_data_grid_doc_navigation.ts create mode 100644 test/functional/apps/discover/_data_grid_doc_table.ts create mode 100644 test/functional/apps/discover/_data_grid_field_data.ts diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 99fadb240335a..7e7c8953fd527 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -262,6 +262,10 @@ Hides the "Time" column in *Discover* and in all saved searches on dashboards. Highlights results in *Discover* and saved searches on dashboards. Highlighting slows requests when working on big documents. +[[doctable-legacy]]`doc_table:legacy`:: +Control the way the Discover's table looks and works. Set this property to `true` to revert to the legacy implementation. + + [float] [[kibana-ml-settings]] ==== Machine learning diff --git a/src/core/public/chrome/ui/header/_index.scss b/src/core/public/chrome/ui/header/_index.scss index 44cd864278325..b11e7e47f4ae7 100644 --- a/src/core/public/chrome/ui/header/_index.scss +++ b/src/core/public/chrome/ui/header/_index.scss @@ -1,5 +1,19 @@ @include euiHeaderAffordForFixed; +.euiDataGrid__restrictBody { + .headerGlobalNav, + .kbnQueryBar { + display: none; + } +} + +.euiDataGrid__restrictBody.euiBody--headerIsFixed { + .euiFlyout { + top: 0; + height: 100%; + } +} + .chrHeaderHelpMenu__version { text-transform: none; } diff --git a/src/plugins/discover/common/index.ts b/src/plugins/discover/common/index.ts index 4334af63539e3..321a102e8d782 100644 --- a/src/plugins/discover/common/index.ts +++ b/src/plugins/discover/common/index.ts @@ -27,4 +27,5 @@ export const FIELDS_LIMIT_SETTING = 'fields:popularLimit'; export const CONTEXT_DEFAULT_SIZE_SETTING = 'context:defaultSize'; export const CONTEXT_STEP_SETTING = 'context:step'; export const CONTEXT_TIE_BREAKER_FIELDS_SETTING = 'context:tieBreakerFields'; +export const DOC_TABLE_LEGACY = 'doc_table:legacy'; export const MODIFY_COLUMNS_ON_SWITCH = 'discover:modifyColumnsOnSwitch'; diff --git a/src/plugins/discover/public/__mocks__/index_pattern.ts b/src/plugins/discover/public/__mocks__/index_pattern.ts index 706118cb71350..f2c12315d4b90 100644 --- a/src/plugins/discover/public/__mocks__/index_pattern.ts +++ b/src/plugins/discover/public/__mocks__/index_pattern.ts @@ -22,29 +22,40 @@ import { IndexPattern } from '../../../data/common'; import { indexPatterns } from '../../../data/public'; const fields = [ + { + name: '_source', + type: '_source', + scripted: false, + filterable: false, + aggregatable: false, + }, { name: '_index', type: 'string', scripted: false, filterable: true, + aggregatable: false, }, { name: 'message', type: 'string', scripted: false, filterable: false, + aggregatable: false, }, { name: 'extension', type: 'string', scripted: false, filterable: true, + aggregatable: true, }, { name: 'bytes', type: 'number', scripted: false, filterable: true, + aggregatable: true, }, { name: 'scripted', @@ -62,16 +73,21 @@ const indexPattern = ({ id: 'the-index-pattern-id', title: 'the-index-pattern-title', metaFields: ['_index', '_score'], + formatField: jest.fn(), flattenHit: undefined, formatHit: jest.fn((hit) => hit._source), fields, - getComputedFields: () => ({}), + getComputedFields: () => ({ docvalueFields: [], scriptFields: {}, storedFields: ['*'] }), getSourceFiltering: () => ({}), getFieldByName: () => ({}), timeFieldName: '', + docvalueFields: [], } as unknown) as IndexPattern; indexPattern.flattenHit = indexPatterns.flattenHitWrapper(indexPattern, indexPattern.metaFields); indexPattern.isTimeBased = () => !!indexPattern.timeFieldName; +indexPattern.formatField = (hit: Record, fieldName: string) => { + return fieldName === '_source' ? hit._source : indexPattern.flattenHit(hit)[fieldName]; +}; export const indexPatternMock = indexPattern; diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 99497d61c716e..639e2212392cc 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -24,7 +24,6 @@ import moment from 'moment'; import dateMath from '@elastic/datemath'; import { i18n } from '@kbn/i18n'; import { createSearchSessionRestorationDataProvider, getState, splitState } from './discover_state'; - import { RequestAdapter } from '../../../../inspector/public'; import { connectToQueryState, @@ -35,6 +34,7 @@ import { import { getSortArray } from './doc_table'; import * as columnActions from './doc_table/actions/columns'; import indexTemplateLegacy from './discover_legacy.html'; +import indexTemplateGrid from './discover_datagrid.html'; import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util'; import { discoverResponseHandler } from './response_handler'; import { @@ -124,7 +124,9 @@ app.config(($routeProvider) => { }; const discoverRoute = { ...defaults, - template: indexTemplateLegacy, + template: getServices().uiSettings.get('doc_table:legacy', true) + ? indexTemplateLegacy + : indexTemplateGrid, reloadOnSearch: false, resolve: { savedObjects: function ($route, Promise) { @@ -340,6 +342,8 @@ function discoverController($element, $route, $scope, $timeout, Promise, uiCapab $scope.minimumVisibleRows = 50; $scope.fetchStatus = fetchStatuses.UNINITIALIZED; $scope.showSaveQuery = uiCapabilities.discover.saveQuery; + $scope.showTimeCol = + !config.get('doc_table:hideTimeColumn', false) && $scope.indexPattern.timeFieldName; let abortController; $scope.$on('$destroy', () => { @@ -414,7 +418,7 @@ function discoverController($element, $route, $scope, $timeout, Promise, uiCapab const query = $scope.searchSource.getField('query') || data.query.queryString.getDefaultQuery(); const sort = getSortArray(savedSearch.sort, $scope.indexPattern); - return { + const defaultState = { query, sort: !sort.length ? getDefaultSort($scope.indexPattern, config.get(SORT_DEFAULT_ORDER_SETTING, 'desc')) @@ -427,6 +431,11 @@ function discoverController($element, $route, $scope, $timeout, Promise, uiCapab interval: 'auto', filters: _.cloneDeep($scope.searchSource.getOwnField('filter')), }; + if (savedSearch.grid) { + defaultState.grid = savedSearch.grid; + } + + return defaultState; } $scope.state.index = $scope.indexPattern.id; @@ -440,6 +449,8 @@ function discoverController($element, $route, $scope, $timeout, Promise, uiCapab indexPatternList: $route.current.locals.savedObjects.ip.list, config: config, setHeaderActionMenu: getHeaderActionMenuMounter(), + filterManager, + setAppState, data, }; @@ -783,6 +794,17 @@ function discoverController($element, $route, $scope, $timeout, Promise, uiCapab const columns = columnActions.moveColumn($scope.state.columns, columnName, newIndex); setAppState({ columns }); }; + + $scope.setColumns = function setColumns(columns) { + // remove first element of columns if it's the configured timeFieldName, which is prepended automatically + const actualColumns = + $scope.indexPattern.timeFieldName && $scope.indexPattern.timeFieldName === columns[0] + ? columns.slice(1) + : columns; + $scope.state = { ...$scope.state, columns: actualColumns }; + setAppState({ columns: actualColumns }); + }; + async function setupVisualization() { // If no timefield has been specified we don't create a histogram of messages if (!getTimeField()) return; diff --git a/src/plugins/discover/public/application/angular/discover_datagrid.html b/src/plugins/discover/public/application/angular/discover_datagrid.html new file mode 100644 index 0000000000000..e59ebbb0fafd0 --- /dev/null +++ b/src/plugins/discover/public/application/angular/discover_datagrid.html @@ -0,0 +1,31 @@ + + + + diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index 7cdcd6cbbca3a..3596c0a2519ed 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -1,6 +1,5 @@ ( + +)); + +export function DiscoverGridEmbeddable(props: DiscoverGridProps) { + return ( + + + + ); +} + +/** + * this is just needed for the embeddable + */ +export function createDiscoverGridDirective(reactDirective: any) { + return reactDirective(DiscoverGridEmbeddable, [ + ['columns', { watchDepth: 'collection' }], + ['indexPattern', { watchDepth: 'reference' }], + ['onAddColumn', { watchDepth: 'reference', wrapApply: false }], + ['onFilter', { watchDepth: 'reference', wrapApply: false }], + ['onRemoveColumn', { watchDepth: 'reference', wrapApply: false }], + ['onSetColumns', { watchDepth: 'reference', wrapApply: false }], + ['onSort', { watchDepth: 'reference', wrapApply: false }], + ['rows', { watchDepth: 'collection' }], + ['sampleSize', { watchDepth: 'reference' }], + ['searchDescription', { watchDepth: 'reference' }], + ['searchTitle', { watchDepth: 'reference' }], + ['settings', { watchDepth: 'reference' }], + ['showTimeCol', { watchDepth: 'value' }], + ['sort', { watchDepth: 'value' }], + ]); +} diff --git a/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts b/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts index cb3cb06aa90a3..6e5d47be987d8 100644 --- a/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts +++ b/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts @@ -21,7 +21,6 @@ import { DiscoverLegacy } from './discover_legacy'; export function createDiscoverLegacyDirective(reactDirective: any) { return reactDirective(DiscoverLegacy, [ - ['addColumn', { watchDepth: 'reference' }], ['fetch', { watchDepth: 'reference' }], ['fetchCounter', { watchDepth: 'reference' }], ['fetchError', { watchDepth: 'reference' }], @@ -30,6 +29,7 @@ export function createDiscoverLegacyDirective(reactDirective: any) { ['hits', { watchDepth: 'reference' }], ['indexPattern', { watchDepth: 'reference' }], ['minimumVisibleRows', { watchDepth: 'reference' }], + ['onAddColumn', { watchDepth: 'reference' }], ['onAddFilter', { watchDepth: 'reference' }], ['onChangeInterval', { watchDepth: 'reference' }], ['onMoveColumn', { watchDepth: 'reference' }], diff --git a/src/plugins/discover/public/application/components/discover.scss b/src/plugins/discover/public/application/components/discover.scss index b17da97a45930..665bd98c232a5 100644 --- a/src/plugins/discover/public/application/components/discover.scss +++ b/src/plugins/discover/public/application/components/discover.scss @@ -35,6 +35,10 @@ discover-app { } } +.dscPageContent { + border: $euiBorderThin; +} + .dscPageContent, .dscPageContent__inner { height: 100%; @@ -46,6 +50,7 @@ discover-app { .dscResultCount { padding: $euiSizeS; + min-height: $euiSize * 3; @include euiBreakpoint('xs', 's') { .dscResultCount__toggle { @@ -76,6 +81,13 @@ discover-app { padding: $euiSizeS; } +// new slimmer layout for data grid +.dscHistogramGrid { + display: flex; + height: $euiSize * 8; + padding: $euiSizeS $euiSizeS 0 $euiSizeS; +} + .dscTable { // SASSTODO: add a monospace modifier to the doc-table component .kbnDocTable__row { diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx new file mode 100644 index 0000000000000..aa756d960e435 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -0,0 +1,321 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import './discover.scss'; +import React, { useState, useRef } from 'react'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiHideFor, + EuiHorizontalRule, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import classNames from 'classnames'; +import { HitsCounter } from './hits_counter'; +import { TimechartHeader } from './timechart_header'; +import { getServices } from '../../kibana_services'; +import { DiscoverUninitialized, DiscoverHistogram } from '../angular/directives'; +import { DiscoverNoResults } from './no_results'; +import { LoadingSpinner } from './loading_spinner/loading_spinner'; +import { search } from '../../../../data/public'; +import { + DiscoverSidebarResponsive, + DiscoverSidebarResponsiveProps, +} from './sidebar/discover_sidebar_responsive'; +import { DiscoverProps } from './discover_legacy'; +import { SortPairArr } from '../angular/doc_table/lib/get_sort'; +import { DiscoverGrid, DiscoverGridProps } from './discover_grid/discover_grid'; + +export const SidebarMemoized = React.memo((props: DiscoverSidebarResponsiveProps) => ( + +)); + +export const DataGridMemoized = React.memo((props: DiscoverGridProps) => ( + +)); + +export function Discover({ + fetch, + fetchCounter, + fetchError, + fieldCounts, + histogramData, + hits, + indexPattern, + onAddColumn, + onAddFilter, + onChangeInterval, + onRemoveColumn, + onSetColumns, + onSort, + opts, + resetQuery, + resultState, + rows, + searchSource, + setIndexPattern, + showSaveQuery, + state, + timefilterUpdateHandler, + timeRange, + topNavMenu, + updateQuery, + updateSavedQueryId, +}: DiscoverProps) { + const scrollableDesktop = useRef(null); + const collapseIcon = useRef(null); + const [toggleOn, toggleChart] = useState(true); + const [isSidebarClosed, setIsSidebarClosed] = useState(false); + const services = getServices(); + const { TopNavMenu } = services.navigation.ui; + const { trackUiMetric } = services; + const { savedSearch, indexPatternList, config } = opts; + const bucketAggConfig = opts.chartAggConfigs?.aggs[1]; + const bucketInterval = + bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig) + ? bucketAggConfig.buckets?.getInterval() + : undefined; + const contentCentered = resultState === 'uninitialized'; + const showTimeCol = !config.get('doc_table:hideTimeColumn', false) && indexPattern.timeFieldName; + const columns = + state.columns && + state.columns.length > 0 && + // check if all columns where removed except the configured timeField (this can't be removed) + !(state.columns.length === 1 && state.columns[0] === indexPattern.timeFieldName) + ? state.columns + : ['_source']; + // if columns include _source this is considered as default view, so you can't remove columns + // until you add a column using Discover's sidebar + const defaultColumns = columns.includes('_source'); + + return ( + + + + +

    + {savedSearch.title} +

    + + + + + + + setIsSidebarClosed(!isSidebarClosed)} + data-test-subj="collapseSideBarButton" + aria-controls="discover-sidebar" + aria-expanded={isSidebarClosed ? 'false' : 'true'} + aria-label={i18n.translate('discover.toggleSidebarAriaLabel', { + defaultMessage: 'Toggle sidebar', + })} + buttonRef={collapseIcon} + /> + + + + + {resultState === 'none' && ( + + )} + {resultState === 'uninitialized' && } + {resultState === 'loading' && } + {resultState === 'ready' && ( + + + + + 0 ? hits : 0} + showResetButton={!!(savedSearch && savedSearch.id)} + onResetQuery={resetQuery} + /> + + {toggleOn && ( + + + + )} + + { + toggleChart(!toggleOn); + }} + > + {toggleOn + ? i18n.translate('discover.hideChart', { + defaultMessage: 'Hide chart', + }) + : i18n.translate('discover.showChart', { + defaultMessage: 'Show chart', + })} + + + + + {toggleOn && opts.timefield && ( + +
    + {opts.chartAggConfigs && histogramData && rows.length !== 0 && ( +
    + +
    + )} +
    + +
    + )} + + + + +
    +

    + +

    + {rows && rows.length && ( +
    + { + const grid = { ...state.grid } || {}; + const newColumns = { ...grid.columns } || {}; + newColumns[colSettings.columnId] = { + width: colSettings.width, + }; + const newGrid = { ...grid, columns: newColumns }; + opts.setAppState({ grid: newGrid }); + }} + /> +
    + )} +
    +
    +
    + )} +
    +
    +
    +
    +
    +
    + ); +} diff --git a/src/plugins/discover/public/application/components/discover_grid/constants.ts b/src/plugins/discover/public/application/components/discover_grid/constants.ts new file mode 100644 index 0000000000000..dec483da8f8a1 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/constants.ts @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +// data types +export const kibanaJSON = 'kibana-json'; +export const geoPoint = 'geo-point'; +export const unknownType = 'unknown'; +export const gridStyle = { + border: 'all', + fontSize: 's', + cellPadding: 's', + rowHover: 'none', +}; + +export const pageSizeArr = [25, 50, 100]; +export const defaultPageSize = 25; +export const toolbarVisibility = { + showColumnSelector: { + allowHide: false, + allowReorder: true, + }, + showStyleSelector: false, +}; diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss b/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss new file mode 100644 index 0000000000000..64a7eda963349 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss @@ -0,0 +1,68 @@ +.dscDiscoverGrid { + width: 100%; + max-width: 100%; + height: 100%; + overflow: hidden; + + .euiDataGrid__controls { + border: none; + border-bottom: $euiBorderThin; + } + + .euiDataGridRowCell:first-of-type, + .euiDataGrid--headerShade.euiDataGrid--bordersAll .euiDataGridHeaderCell:first-of-type { + border-left: none; + border-right: none; + } + + .euiDataGridRowCell:last-of-type, + .euiDataGridHeaderCell:last-of-type { + border-right: none; + } +} + +.dscDiscoverGrid__footer { + background-color: $euiColorLightShade; + padding: $euiSize / 2 $euiSize; + margin-top: $euiSize / 4; + text-align: center; +} + +.dscTable__flyoutHeader { + white-space: nowrap; +} + +// We only truncate if the cell is not a control column. +.euiDataGridHeader { + .euiDataGridHeaderCell__content { + @include euiTextTruncate; + overflow: hidden; + white-space: nowrap; + flex-grow: 1; + } + + .euiDataGridHeaderCell__popover { + flex-grow: 0; + flex-basis: auto; + width: auto; + padding-left: $euiSizeXS; + } +} + +.euiDataGridRowCell--numeric { + text-align: right; +} + +.euiDataGrid__noResults { + display: flex; + flex-direction: column; + justify-content: center; + flex: 1 0 100%; + text-align: center; + height: 100%; + width: 100%; +} + +.dscFormatSource { + @include euiTextTruncate; +} diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx new file mode 100644 index 0000000000000..9588f74ed2bc2 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -0,0 +1,336 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useCallback, useMemo, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import './discover_grid.scss'; +import { + EuiDataGridSorting, + EuiDataGridStyle, + EuiDataGridProps, + EuiDataGrid, + EuiIcon, + EuiScreenReaderOnly, + EuiSpacer, + EuiText, + htmlIdGenerator, +} from '@elastic/eui'; +import { IndexPattern } from '../../../kibana_services'; +import { DocViewFilterFn, ElasticSearchHit } from '../../doc_views/doc_views_types'; +import { getPopoverContents, getSchemaDetectors } from './discover_grid_schema'; +import { DiscoverGridFlyout } from './discover_grid_flyout'; +import { DiscoverGridContext } from './discover_grid_context'; +import { getRenderCellValueFn } from './get_render_cell_value'; +import { DiscoverGridSettings } from './types'; +import { SortPairArr } from '../../angular/doc_table/lib/get_sort'; +import { + getEuiGridColumns, + getLeadControlColumns, + getVisibleColumns, +} from './discover_grid_columns'; +import { defaultPageSize, gridStyle, pageSizeArr, toolbarVisibility } from './constants'; +import { DiscoverServices } from '../../../build_services'; + +interface SortObj { + id: string; + direction: string; +} + +export interface DiscoverGridProps { + /** + * Determines which element labels the grid for ARIA + */ + ariaLabelledBy: string; + /** + * Determines which columns are displayed + */ + columns: string[]; + /** + * Determines whether the given columns are the default ones, so parts of the document + * are displayed (_source) with limited actions (cannor move, remove columns) + * Implemented for matching with legacy behavior + */ + defaultColumns: boolean; + /** + * The used index pattern + */ + indexPattern: IndexPattern; + /** + * Function used to add a column in the document flyout + */ + onAddColumn: (column: string) => void; + /** + * Function to add a filter in the grid cell or document flyout + */ + onFilter: DocViewFilterFn; + /** + * Function used in the grid header and flyout to remove a column + * @param column + */ + onRemoveColumn: (column: string) => void; + /** + * Function triggered when a column is resized by the user + */ + onResize?: (colSettings: { columnId: string; width: number }) => void; + /** + * Function to set all columns + */ + onSetColumns: (columns: string[]) => void; + /** + * function to change sorting of the documents + */ + onSort: (sort: string[][]) => void; + /** + * Array of documents provided by Elasticsearch + */ + rows?: ElasticSearchHit[]; + /** + * The max size of the documents returned by Elasticsearch + */ + sampleSize: number; + /** + * Grid display settings persisted in Elasticsearch (e.g. column width) + */ + settings?: DiscoverGridSettings; + /** + * Saved search description + */ + searchDescription?: string; + /** + * Saved search title + */ + searchTitle?: string; + /** + * Discover plugin services + */ + services: DiscoverServices; + /** + * Determines whether the time columns should be displayed (legacy settings) + */ + showTimeCol: boolean; + /** + * Current sort setting + */ + sort: SortPairArr[]; +} + +export const EuiDataGridMemoized = React.memo((props: EuiDataGridProps) => { + return ; +}); + +export const DiscoverGrid = ({ + ariaLabelledBy, + columns, + defaultColumns, + indexPattern, + onAddColumn, + onFilter, + onRemoveColumn, + onResize, + onSetColumns, + onSort, + rows, + sampleSize, + searchDescription, + searchTitle, + services, + settings, + showTimeCol, + sort, +}: DiscoverGridProps) => { + const [expanded, setExpanded] = useState(undefined); + + /** + * Pagination + */ + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: defaultPageSize }); + const rowCount = useMemo(() => (rows ? rows.length : 0), [rows]); + const pageCount = useMemo(() => Math.ceil(rowCount / pagination.pageSize), [ + rowCount, + pagination, + ]); + const isOnLastPage = pagination.pageIndex === pageCount - 1; + + const paginationObj = useMemo(() => { + const onChangeItemsPerPage = (pageSize: number) => + setPagination((paginationData) => ({ ...paginationData, pageSize })); + + const onChangePage = (pageIndex: number) => + setPagination((paginationData) => ({ ...paginationData, pageIndex })); + + return { + onChangeItemsPerPage, + onChangePage, + pageIndex: pagination.pageIndex > pageCount - 1 ? 0 : pagination.pageIndex, + pageSize: pagination.pageSize, + pageSizeOptions: pageSizeArr, + }; + }, [pagination, pageCount]); + + /** + * Sorting + */ + const sortingColumns = useMemo(() => sort.map(([id, direction]) => ({ id, direction })), [sort]); + + const onTableSort = useCallback( + (sortingColumnsData) => { + onSort(sortingColumnsData.map(({ id, direction }: SortObj) => [id, direction])); + }, + [onSort] + ); + + /** + * Cell rendering + */ + const renderCellValue = useMemo( + () => + getRenderCellValueFn( + indexPattern, + rows, + rows ? rows.map((hit) => indexPattern.flattenHit(hit)) : [] + ), + [rows, indexPattern] + ); + + /** + * Render variables + */ + const showDisclaimer = rowCount === sampleSize && isOnLastPage; + const randomId = useMemo(() => htmlIdGenerator()(), []); + + const euiGridColumns = useMemo( + () => getEuiGridColumns(columns, settings, indexPattern, showTimeCol, defaultColumns), + [columns, indexPattern, showTimeCol, settings, defaultColumns] + ); + const schemaDetectors = useMemo(() => getSchemaDetectors(), []); + const popoverContents = useMemo(() => getPopoverContents(), []); + const columnsVisibility = useMemo( + () => ({ + visibleColumns: getVisibleColumns(columns, indexPattern, showTimeCol) as string[], + setVisibleColumns: (newColumns: string[]) => { + onSetColumns(newColumns); + }, + }), + [columns, indexPattern, showTimeCol, onSetColumns] + ); + const sorting = useMemo(() => ({ columns: sortingColumns, onSort: onTableSort }), [ + sortingColumns, + onTableSort, + ]); + const lead = useMemo(() => getLeadControlColumns(), []); + + if (!rowCount) { + return ( +
    + + + + + +
    + ); + } + + return ( + + <> + { + if (onResize) { + onResize(col); + } + }} + pagination={paginationObj} + popoverContents={popoverContents} + renderCellValue={renderCellValue} + rowCount={rowCount} + schemaDetectors={schemaDetectors} + sorting={sorting as EuiDataGridSorting} + toolbarVisibility={ + defaultColumns + ? { + ...toolbarVisibility, + showColumnSelector: false, + } + : toolbarVisibility + } + /> + + {showDisclaimer && ( +

    + + + + +

    + )} + {searchTitle && ( + +

    + {searchDescription ? ( + + ) : ( + + )} +

    +
    + )} + {expanded && ( + setExpanded(undefined)} + services={services} + /> + )} + +
    + ); +}; diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.test.tsx new file mode 100644 index 0000000000000..a85583f66c6fa --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.test.tsx @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { FilterInBtn, FilterOutBtn } from './discover_grid_cell_actions'; +import { DiscoverGridContext } from './discover_grid_context'; + +import { indexPatternMock } from '../../../__mocks__/index_pattern'; +import { esHits } from '../../../__mocks__/es_hits'; +import { EuiButton } from '@elastic/eui'; + +describe('Discover cell actions ', function () { + it('triggers filter function when FilterInBtn is clicked', async () => { + const contextMock = { + expanded: undefined, + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + }; + + const component = mountWithIntl( + + } + rowIndex={1} + columnId={'extension'} + isExpanded={false} + closePopover={jest.fn()} + /> + + ); + const button = findTestSubject(component, 'filterForButton'); + await button.simulate('click'); + expect(contextMock.onFilter).toHaveBeenCalledWith('extension', 'jpg', '+'); + }); + it('triggers filter function when FilterOutBtn is clicked', async () => { + const contextMock = { + expanded: undefined, + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + }; + + const component = mountWithIntl( + + } + rowIndex={1} + columnId={'extension'} + isExpanded={false} + closePopover={jest.fn()} + /> + + ); + const button = findTestSubject(component, 'filterOutButton'); + await button.simulate('click'); + expect(contextMock.onFilter).toHaveBeenCalledWith('extension', 'jpg', '-'); + }); +}); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx new file mode 100644 index 0000000000000..ef56166258c9b --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx @@ -0,0 +1,97 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useContext } from 'react'; +import { EuiDataGridColumnCellActionProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { IndexPatternField } from '../../../../../data/common/index_patterns/fields'; +import { DiscoverGridContext } from './discover_grid_context'; + +export const FilterInBtn = ({ + Component, + rowIndex, + columnId, +}: EuiDataGridColumnCellActionProps) => { + const context = useContext(DiscoverGridContext); + const buttonTitle = i18n.translate('discover.grid.filterForAria', { + defaultMessage: 'Filter for this {value}', + values: { value: columnId }, + }); + + return ( + { + const row = context.rows[rowIndex]; + const flattened = context.indexPattern.flattenHit(row); + + if (flattened) { + context.onFilter(columnId, flattened[columnId], '+'); + } + }} + iconType="plusInCircle" + aria-label={buttonTitle} + title={buttonTitle} + data-test-subj="filterForButton" + > + {i18n.translate('discover.grid.filterFor', { + defaultMessage: 'Filter for', + })} + + ); +}; + +export const FilterOutBtn = ({ + Component, + rowIndex, + columnId, +}: EuiDataGridColumnCellActionProps) => { + const context = useContext(DiscoverGridContext); + const buttonTitle = i18n.translate('discover.grid.filterOutAria', { + defaultMessage: 'Filter out this {value}', + values: { value: columnId }, + }); + + return ( + { + const row = context.rows[rowIndex]; + const flattened = context.indexPattern.flattenHit(row); + + if (flattened) { + context.onFilter(columnId, flattened[columnId], '-'); + } + }} + iconType="minusInCircle" + aria-label={buttonTitle} + title={buttonTitle} + data-test-subj="filterOutButton" + > + {i18n.translate('discover.grid.filterOut', { + defaultMessage: 'Filter out', + })} + + ); +}; + +export function buildCellActions(field: IndexPatternField) { + if (!field.aggregatable && !field.searchable) { + return undefined; + } + + return [FilterInBtn, FilterOutBtn]; +} diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx new file mode 100644 index 0000000000000..dad7e1363fdd9 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx @@ -0,0 +1,154 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { indexPatternMock } from '../../../__mocks__/index_pattern'; +import { getEuiGridColumns } from './discover_grid_columns'; +import { indexPatternWithTimefieldMock } from '../../../__mocks__/index_pattern_with_timefield'; + +describe('Discover grid columns ', function () { + it('returns eui grid columns without time column', async () => { + const actual = getEuiGridColumns(['extension', 'message'], {}, indexPatternMock, false, false); + expect(actual).toMatchInlineSnapshot(` + Array [ + Object { + "actions": Object { + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": undefined, + "display": undefined, + "id": "extension", + "isSortable": undefined, + "schema": "unknown", + }, + Object { + "actions": Object { + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": undefined, + "display": undefined, + "id": "message", + "isSortable": undefined, + "schema": "unknown", + }, + ] + `); + }); + it('returns eui grid columns without time column showing default columns', async () => { + const actual = getEuiGridColumns( + ['extension', 'message'], + {}, + indexPatternWithTimefieldMock, + false, + true + ); + expect(actual).toMatchInlineSnapshot(` + Array [ + Object { + "actions": Object { + "showHide": false, + "showMoveLeft": false, + "showMoveRight": false, + }, + "cellActions": undefined, + "display": undefined, + "id": "extension", + "isSortable": undefined, + "schema": "unknown", + }, + Object { + "actions": Object { + "showHide": false, + "showMoveLeft": false, + "showMoveRight": false, + }, + "cellActions": undefined, + "display": undefined, + "id": "message", + "isSortable": undefined, + "schema": "unknown", + }, + ] + `); + }); + it('returns eui grid columns with time column', async () => { + const actual = getEuiGridColumns( + ['extension', 'message'], + {}, + indexPatternWithTimefieldMock, + true, + false + ); + expect(actual).toMatchInlineSnapshot(` + Array [ + Object { + "actions": Object { + "showHide": false, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": undefined, + "display": "Time (timestamp)", + "id": "timestamp", + "initialWidth": 180, + "isSortable": undefined, + "schema": "unknown", + }, + Object { + "actions": Object { + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": undefined, + "display": undefined, + "id": "extension", + "isSortable": undefined, + "schema": "unknown", + }, + Object { + "actions": Object { + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": undefined, + "display": undefined, + "id": "message", + "isSortable": undefined, + "schema": "unknown", + }, + ] + `); + }); +}); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx new file mode 100644 index 0000000000000..1cf9c84405a61 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx @@ -0,0 +1,122 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiDataGridColumn, EuiScreenReaderOnly } from '@elastic/eui'; +import { ExpandButton } from './discover_grid_expand_button'; +import { DiscoverGridSettings } from './types'; +import { IndexPattern } from '../../../../../data/common/index_patterns/index_patterns'; +import { buildCellActions } from './discover_grid_cell_actions'; +import { getSchemaByKbnType } from './discover_grid_schema'; + +export function getLeadControlColumns() { + return [ + { + id: 'openDetails', + width: 32, + headerCellRender: () => ( + + + {i18n.translate('discover.controlColumnHeader', { + defaultMessage: 'Control column', + })} + + + ), + rowCellRender: ExpandButton, + }, + ]; +} + +export function buildEuiGridColumn( + columnName: string, + columnWidth: number | undefined = 0, + indexPattern: IndexPattern, + defaultColumns: boolean +) { + const timeString = i18n.translate('discover.timeLabel', { + defaultMessage: 'Time', + }); + const indexPatternField = indexPattern.getFieldByName(columnName); + const column: EuiDataGridColumn = { + id: columnName, + schema: getSchemaByKbnType(indexPatternField?.type), + isSortable: indexPatternField?.sortable, + display: indexPatternField?.displayName, + actions: { + showHide: + defaultColumns || columnName === indexPattern.timeFieldName + ? false + : { + label: i18n.translate('discover.removeColumnLabel', { + defaultMessage: 'Remove column', + }), + iconType: 'cross', + }, + showMoveLeft: !defaultColumns, + showMoveRight: !defaultColumns, + }, + cellActions: indexPatternField ? buildCellActions(indexPatternField) : [], + }; + + if (column.id === indexPattern.timeFieldName) { + column.display = `${timeString} (${indexPattern.timeFieldName})`; + column.initialWidth = 180; + } + if (columnWidth > 0) { + column.initialWidth = Number(columnWidth); + } + return column; +} + +export function getEuiGridColumns( + columns: string[], + settings: DiscoverGridSettings | undefined, + indexPattern: IndexPattern, + showTimeCol: boolean, + defaultColumns: boolean +) { + const timeFieldName = indexPattern.timeFieldName; + const getColWidth = (column: string) => settings?.columns?.[column]?.width ?? 0; + + if (showTimeCol && indexPattern.timeFieldName && !columns.find((col) => col === timeFieldName)) { + const usedColumns = [indexPattern.timeFieldName, ...columns]; + return usedColumns.map((column) => + buildEuiGridColumn(column, getColWidth(column), indexPattern, defaultColumns) + ); + } + + return columns.map((column) => + buildEuiGridColumn(column, getColWidth(column), indexPattern, defaultColumns) + ); +} + +export function getVisibleColumns( + columns: string[], + indexPattern: IndexPattern, + showTimeCol: boolean +) { + const timeFieldName = indexPattern.timeFieldName; + + if (showTimeCol && !columns.find((col) => col === timeFieldName)) { + return [timeFieldName, ...columns]; + } + + return columns; +} diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_context.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_context.tsx new file mode 100644 index 0000000000000..dcc404a0e48df --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_context.tsx @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { DocViewFilterFn, ElasticSearchHit } from '../../doc_views/doc_views_types'; +import { IndexPattern } from '../../../kibana_services'; + +export interface GridContext { + expanded: ElasticSearchHit | undefined; + setExpanded: (hit: ElasticSearchHit | undefined) => void; + rows: ElasticSearchHit[]; + onFilter: DocViewFilterFn; + indexPattern: IndexPattern; + isDarkMode: boolean; +} + +const defaultContext = ({} as unknown) as GridContext; + +export const DiscoverGridContext = React.createContext(defaultContext); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx new file mode 100644 index 0000000000000..82fcad8c2cd6f --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { ExpandButton } from './discover_grid_expand_button'; +import { DiscoverGridContext } from './discover_grid_context'; +import { indexPatternMock } from '../../../__mocks__/index_pattern'; +import { esHits } from '../../../__mocks__/es_hits'; + +describe('Discover grid view button ', function () { + it('when no document is expanded, setExpanded is called with current document', async () => { + const contextMock = { + expanded: undefined, + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + }; + + const component = mountWithIntl( + + + + ); + const button = findTestSubject(component, 'docTableExpandToggleColumn'); + await button.simulate('click'); + expect(contextMock.setExpanded).toHaveBeenCalledWith(esHits[0]); + }); + it('when the current document is expanded, setExpanded is called with undefined', async () => { + const contextMock = { + expanded: esHits[0], + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + }; + + const component = mountWithIntl( + + + + ); + const button = findTestSubject(component, 'docTableExpandToggleColumn'); + await button.simulate('click'); + expect(contextMock.setExpanded).toHaveBeenCalledWith(undefined); + }); + it('when another document is expanded, setExpanded is called with the current document', async () => { + const contextMock = { + expanded: esHits[0], + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + }; + + const component = mountWithIntl( + + + + ); + const button = findTestSubject(component, 'docTableExpandToggleColumn'); + await button.simulate('click'); + expect(contextMock.setExpanded).toHaveBeenCalledWith(esHits[1]); + }); +}); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.tsx new file mode 100644 index 0000000000000..d4a3fe85e34ef --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.tsx @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useContext, useEffect } from 'react'; +import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; +import themeDark from '@elastic/eui/dist/eui_theme_dark.json'; +import themeLight from '@elastic/eui/dist/eui_theme_light.json'; +import { i18n } from '@kbn/i18n'; +import { DiscoverGridContext } from './discover_grid_context'; +/** + * Button to expand a given row + */ +export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { + const { expanded, setExpanded, rows, isDarkMode } = useContext(DiscoverGridContext); + const current = rows[rowIndex]; + useEffect(() => { + if (expanded && current && expanded._id === current._id) { + setCellProps({ + style: { + backgroundColor: isDarkMode ? themeDark.euiColorHighlight : themeLight.euiColorHighlight, + }, + }); + } else { + setCellProps({ style: undefined }); + } + }, [expanded, current, setCellProps, isDarkMode]); + + const isCurrentRowExpanded = current === expanded; + const buttonLabel = i18n.translate('discover.grid.viewDoc', { + defaultMessage: 'Toggle dialog with details', + }); + + return ( + + setExpanded(isCurrentRowExpanded ? undefined : current)} + color={isCurrentRowExpanded ? 'primary' : 'subdued'} + iconType={isCurrentRowExpanded ? 'minimize' : 'expand'} + isSelected={isCurrentRowExpanded} + /> + + ); +}; diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx new file mode 100644 index 0000000000000..79ad98ae2babe --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.tsx @@ -0,0 +1,143 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiButtonEmpty, + EuiText, + EuiSpacer, + EuiPortal, +} from '@elastic/eui'; +import { DocViewer } from '../doc_viewer/doc_viewer'; +import { IndexPattern } from '../../../kibana_services'; +import { DocViewFilterFn, ElasticSearchHit } from '../../doc_views/doc_views_types'; +import { DiscoverServices } from '../../../build_services'; +import { getContextUrl } from '../../helpers/get_context_url'; + +interface Props { + columns: string[]; + hit: ElasticSearchHit; + indexPattern: IndexPattern; + onAddColumn: (column: string) => void; + onClose: () => void; + onFilter: DocViewFilterFn; + onRemoveColumn: (column: string) => void; + services: DiscoverServices; +} + +/** + * Flyout displaying an expanded Elasticsearch document + */ +export function DiscoverGridFlyout({ + hit, + indexPattern, + columns, + onFilter, + onClose, + onRemoveColumn, + onAddColumn, + services, +}: Props) { + return ( + + + + +

    + {i18n.translate('discover.grid.tableRow.detailHeading', { + defaultMessage: 'Expanded document', + })} +

    +
    + + + + + + + {i18n.translate('discover.grid.tableRow.viewText', { + defaultMessage: 'View:', + })} + + + + + + {i18n.translate('discover.grid.tableRow.viewSingleDocumentLinkTextSimple', { + defaultMessage: 'Single document', + })} + + + {indexPattern.isTimeBased() && indexPattern.id && ( + + + {i18n.translate('discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple', { + defaultMessage: 'Surrounding documents', + })} + + + )} + +
    + + { + onFilter(mapping, value, mode); + onClose(); + }} + onRemoveColumn={(columnName: string) => { + onRemoveColumn(columnName); + onClose(); + }} + onAddColumn={(columnName: string) => { + onAddColumn(columnName); + onClose(); + }} + /> + +
    +
    + ); +} diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_schema.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_schema.tsx new file mode 100644 index 0000000000000..aa87d3982fa06 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_schema.tsx @@ -0,0 +1,103 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { ReactNode } from 'react'; +import { EuiCodeBlock } from '@elastic/eui'; +import { geoPoint, kibanaJSON, unknownType } from './constants'; +import { KBN_FIELD_TYPES } from '../../../../../data/common'; + +export function getSchemaByKbnType(kbnType: string | undefined) { + // Default DataGrid schemas: boolean, numeric, datetime, json, currency, string + switch (kbnType) { + case KBN_FIELD_TYPES.IP: + case KBN_FIELD_TYPES.GEO_SHAPE: + case KBN_FIELD_TYPES.NUMBER: + return 'numeric'; + case KBN_FIELD_TYPES.BOOLEAN: + return 'boolean'; + case KBN_FIELD_TYPES.STRING: + return 'string'; + case KBN_FIELD_TYPES.DATE: + return 'datetime'; + case KBN_FIELD_TYPES._SOURCE: + return kibanaJSON; + case KBN_FIELD_TYPES.GEO_POINT: + return geoPoint; + default: + return unknownType; + } +} + +export function getSchemaDetectors() { + return [ + { + type: kibanaJSON, + detector() { + return 0; // this schema is always explicitly defined + }, + sortTextAsc: '', + sortTextDesc: '', + icon: '', + color: '', + }, + { + type: unknownType, + detector() { + return 0; // this schema is always explicitly defined + }, + sortTextAsc: '', + sortTextDesc: '', + icon: '', + color: '', + }, + { + type: geoPoint, + detector() { + return 0; // this schema is always explicitly defined + }, + sortTextAsc: '', + sortTextDesc: '', + icon: 'tokenGeo', + }, + ]; +} + +/** + * Returns custom popover content for certain schemas + */ +export function getPopoverContents() { + return { + [geoPoint]: ({ children }: { children: ReactNode }) => { + return {children}; + }, + [unknownType]: ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); + }, + [kibanaJSON]: ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); + }, + }; +} diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx new file mode 100644 index 0000000000000..d9896f4c53907 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx @@ -0,0 +1,132 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { shallow } from 'enzyme'; +import { getRenderCellValueFn } from './get_render_cell_value'; +import { indexPatternMock } from '../../../__mocks__/index_pattern'; +const rows = [ + { + _id: '1', + _index: 'test', + _type: 'test', + _score: 1, + _source: { bytes: 100 }, + }, +]; + +describe('Discover grid cell rendering', function () { + it('renders bytes column correctly', () => { + const DiscoverGridCellValue = getRenderCellValueFn( + indexPatternMock, + rows, + rows.map((row) => indexPatternMock.flattenHit(row)) + ); + const component = shallow( + + ); + expect(component.html()).toMatchInlineSnapshot(`"100"`); + }); + it('renders _source column correctly', () => { + const DiscoverGridCellValue = getRenderCellValueFn( + indexPatternMock, + rows, + rows.map((row) => indexPatternMock.flattenHit(row)) + ); + const component = shallow( + + ); + expect(component.html()).toMatchInlineSnapshot( + `"
    bytes
    100
    "` + ); + }); + + it('renders _source column correctly when isDetails is set to true', () => { + const DiscoverGridCellValue = getRenderCellValueFn( + indexPatternMock, + rows, + rows.map((row) => indexPatternMock.flattenHit(row)) + ); + const component = shallow( + + ); + expect(component.html()).toMatchInlineSnapshot(` + "{ + "bytes": 100 + }" + `); + }); + + it('renders correctly when invalid row is given', () => { + const DiscoverGridCellValue = getRenderCellValueFn( + indexPatternMock, + rows, + rows.map((row) => indexPatternMock.flattenHit(row)) + ); + const component = shallow( + + ); + expect(component.html()).toMatchInlineSnapshot(`"-"`); + }); + it('renders correctly when invalid column is given', () => { + const DiscoverGridCellValue = getRenderCellValueFn( + indexPatternMock, + rows, + rows.map((row) => indexPatternMock.flattenHit(row)) + ); + const component = shallow( + + ); + expect(component.html()).toMatchInlineSnapshot(`"-"`); + }); +}); diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx new file mode 100644 index 0000000000000..2157e778f84db --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx @@ -0,0 +1,116 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { Fragment, useContext, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import themeLight from '@elastic/eui/dist/eui_theme_light.json'; +import themeDark from '@elastic/eui/dist/eui_theme_dark.json'; + +import { + EuiDataGridCellValueElementProps, + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, +} from '@elastic/eui'; +import { IndexPattern } from '../../../kibana_services'; +import { ElasticSearchHit } from '../../doc_views/doc_views_types'; +import { DiscoverGridContext } from './discover_grid_context'; + +export const getRenderCellValueFn = ( + indexPattern: IndexPattern, + rows: ElasticSearchHit[] | undefined, + rowsFlattened: Array> +) => ({ rowIndex, columnId, isDetails, setCellProps }: EuiDataGridCellValueElementProps) => { + const row = rows ? (rows[rowIndex] as Record) : undefined; + const rowFlattened = rowsFlattened + ? (rowsFlattened[rowIndex] as Record) + : undefined; + + const field = indexPattern.fields.getByName(columnId); + const ctx = useContext(DiscoverGridContext); + + useEffect(() => { + if (ctx.expanded && row && ctx.expanded._id === row._id) { + setCellProps({ + style: { + backgroundColor: ctx.isDarkMode + ? themeDark.euiColorHighlight + : themeLight.euiColorHighlight, + }, + }); + } else { + setCellProps({ style: undefined }); + } + }, [ctx, row, setCellProps]); + + if (typeof row === 'undefined' || typeof rowFlattened === 'undefined') { + return -; + } + + if (field && field.type === '_source') { + if (isDetails) { + // nicely formatted JSON for the expanded view + return {JSON.stringify(row[columnId], null, 2)}; + } + const formatted = indexPattern.formatHit(row); + + return ( + + {Object.keys(formatted).map((key) => ( + + {key} + + + ))} + + ); + } + + if (!field?.type && rowFlattened && typeof rowFlattened[columnId] === 'object') { + if (isDetails) { + // nicely formatted JSON for the expanded view + return {JSON.stringify(rowFlattened[columnId], null, 2)}; + } + + return {JSON.stringify(rowFlattened[columnId])}; + } + + if (field?.type === 'geo_point' && rowFlattened && rowFlattened[columnId]) { + const valueFormatted = rowFlattened[columnId] as { lat: number; lon: number }; + return ( +
    + {i18n.translate('discover.latitudeAndLongitude', { + defaultMessage: 'Lat: {lat} Lon: {lon}', + values: { + lat: valueFormatted?.lat, + lon: valueFormatted?.lon, + }, + })} +
    + ); + } + + const valueFormatted = indexPattern.formatField(row, columnId); + if (typeof valueFormatted === 'undefined') { + return -; + } + return ( + // eslint-disable-next-line react/no-danger + + ); +}; diff --git a/src/plugins/discover/public/application/components/discover_grid/types.ts b/src/plugins/discover/public/application/components/discover_grid/types.ts new file mode 100644 index 0000000000000..3d57dbffe924e --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/types.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * User configurable state of data grid, persisted in saved search + */ +export interface DiscoverGridSettings { + columns?: Record; +} + +export interface DiscoverGridSettingsColumn { + width?: number; +} diff --git a/src/plugins/discover/public/application/components/discover_legacy.test.tsx b/src/plugins/discover/public/application/components/discover_legacy.test.tsx index e2f4ba7ab6e2e..bad5c1d2e532d 100644 --- a/src/plugins/discover/public/application/components/discover_legacy.test.tsx +++ b/src/plugins/discover/public/application/components/discover_legacy.test.tsx @@ -67,7 +67,6 @@ function getProps(indexPattern: IndexPattern) { } as unknown) as DiscoverServices; return { - addColumn: jest.fn(), fetch: jest.fn(), fetchCounter: 0, fetchError: undefined, @@ -75,6 +74,7 @@ function getProps(indexPattern: IndexPattern) { hits: esHits.length, indexPattern, minimumVisibleRows: 10, + onAddColumn: jest.fn(), onAddFilter: jest.fn(), onChangeInterval: jest.fn(), onMoveColumn: jest.fn(), diff --git a/src/plugins/discover/public/application/components/discover_legacy.tsx b/src/plugins/discover/public/application/components/discover_legacy.tsx index d228be66990bd..436a145024437 100644 --- a/src/plugins/discover/public/application/components/discover_legacy.tsx +++ b/src/plugins/discover/public/application/components/discover_legacy.tsx @@ -63,46 +63,161 @@ import { import { DocViewFilterFn, ElasticSearchHit } from '../doc_views/doc_views_types'; export interface DiscoverProps { - addColumn: (column: string) => void; + /** + * Function to fetch documents from Elasticsearch + */ fetch: () => void; + /** + * Counter how often data was fetched (used for testing) + */ fetchCounter: number; + /** + * Error in case of a failing document fetch + */ fetchError?: Error; + /** + * Statistics by fields calculated using the fetched documents + */ fieldCounts: Record; + /** + * Histogram aggregation data + */ histogramData?: Chart; + /** + * Number of documents found by recent fetch + */ hits: number; + /** + * Current IndexPattern + */ indexPattern: IndexPattern; + /** + * Value needed for legacy "infinite" loading functionality + * Determins how much records are rendered using the legacy table + * Increased when scrolling down + */ minimumVisibleRows: number; + /** + * Function to add a column to state + */ + onAddColumn: (column: string) => void; + /** + * Function to add a filter to state + */ onAddFilter: DocViewFilterFn; + /** + * Function to change the used time interval of the date histogram + */ onChangeInterval: (interval: string) => void; + /** + * Function to move a given column to a given index, used in legacy table + */ onMoveColumn: (columns: string, newIdx: number) => void; + /** + * Function to remove a given column from state + */ onRemoveColumn: (column: string) => void; + /** + * Function to replace columns in state + */ onSetColumns: (columns: string[]) => void; + /** + * Function to scroll down the legacy table to the bottom + */ onSkipBottomButtonClick: () => void; + /** + * Function to change sorting of the table, triggers a fetch + */ onSort: (sort: string[][]) => void; opts: { + /** + * Date histogram aggregation config + */ chartAggConfigs?: AggConfigs; + /** + * Client of uiSettings + */ config: IUiSettingsClient; + /** + * Data plugin + */ data: DataPublicPluginStart; - fixedScroll: (el: HTMLElement) => void; + /** + * Data plugin filter manager + */ filterManager: FilterManager; + /** + * List of available index patterns + */ indexPatternList: Array>; + /** + * The number of documents that can be displayed in the table/grid + */ sampleSize: number; + /** + * Current instance of SavedSearch + */ savedSearch: SavedSearch; + /** + * Function to set the header menu + */ setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; + /** + * Timefield of the currently used index pattern + */ timefield: string; + /** + * Function to set the current state + */ setAppState: (state: Partial) => void; }; + /** + * Function to reset the current query + */ resetQuery: () => void; + /** + * Current state of the actual query, one of 'uninitialized', 'loading' ,'ready', 'none' + */ resultState: string; + /** + * Array of document of the recent successful search request + */ rows: ElasticSearchHit[]; + /** + * Instance of SearchSource, the high level search API + */ searchSource: ISearchSource; + /** + * Function to change the current index pattern + */ setIndexPattern: (id: string) => void; + /** + * Determines whether the user should be able to use the save query feature + */ showSaveQuery: boolean; + /** + * Current app state of URL + */ state: AppState; + /** + * Function to update the time filter + */ timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; + /** + * Currently selected time range + */ timeRange?: { from: string; to: string }; + /** + * Menu data of top navigation (New, save ...) + */ topNavMenu: TopNavMenuData[]; + /** + * Function to update the actual query + */ updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; + /** + * Function to update the actual savedQuery id + */ updateSavedQueryId: (savedQueryId?: string) => void; } @@ -114,7 +229,6 @@ export const SidebarMemoized = React.memo((props: DiscoverSidebarResponsiveProps )); export function DiscoverLegacy({ - addColumn, fetch, fetchCounter, fieldCounts, @@ -123,6 +237,7 @@ export function DiscoverLegacy({ hits, indexPattern, minimumVisibleRows, + onAddColumn, onAddFilter, onChangeInterval, onMoveColumn, @@ -192,7 +307,7 @@ export function DiscoverLegacy({ fieldCounts={fieldCounts} hits={rows} indexPatternList={indexPatternList} - onAddField={addColumn} + onAddField={onAddColumn} onAddFilter={onAddFilter} onRemoveField={onRemoveColumn} selectedIndexPattern={searchSource && searchSource.getField('index')} @@ -206,6 +321,8 @@ export function DiscoverLegacy({ setIsSidebarClosed(!isSidebarClosed)} data-test-subj="collapseSideBarButton" aria-controls="discover-sidebar" @@ -335,7 +452,7 @@ export function DiscoverLegacy({ sort={state.sort || []} searchDescription={opts.savedSearch.description} searchTitle={opts.savedSearch.lastSavedTitle} - onAddColumn={addColumn} + onAddColumn={onAddColumn} onFilter={onAddFilter} onMoveColumn={onMoveColumn} onRemoveColumn={onRemoveColumn} diff --git a/src/plugins/discover/public/application/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap b/src/plugins/discover/public/application/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap index b5bd961037e21..d02b484a06a49 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap +++ b/src/plugins/discover/public/application/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap @@ -6,6 +6,7 @@ exports[`Render with 3 different tabs 1`] = ` > - +
    ); } diff --git a/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap b/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap index 2fa96f9372380..6b5e45f8a0448 100644 --- a/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap +++ b/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap @@ -31,7 +31,7 @@ exports[`FieldName renders a geo field 1`] = `
    `; -exports[`FieldName renders a number field by providing a field record, useShortDots is set to false 1`] = ` +exports[`FieldName renders a number field by providing a field record 1`] = `
    diff --git a/src/plugins/discover/public/application/components/field_name/field_name.test.tsx b/src/plugins/discover/public/application/components/field_name/field_name.test.tsx index 0deddce1c40a8..248191acf9ab9 100644 --- a/src/plugins/discover/public/application/components/field_name/field_name.test.tsx +++ b/src/plugins/discover/public/application/components/field_name/field_name.test.tsx @@ -27,7 +27,7 @@ test('FieldName renders a string field by providing fieldType and fieldName', () expect(component).toMatchSnapshot(); }); -test('FieldName renders a number field by providing a field record, useShortDots is set to false', () => { +test('FieldName renders a number field by providing a field record', () => { const component = render(); expect(component).toMatchSnapshot(); }); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx index 391e15485f074..0957ee101bd27 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx @@ -56,7 +56,6 @@ function getComponent({ }: { selected?: boolean; showDetails?: boolean; - useShortDots?: boolean; field?: IndexPatternField; }) { const indexPattern = getStubIndexPattern( diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts index d4670a1e76011..22cacae4c3b45 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts @@ -19,51 +19,58 @@ import { groupFields } from './group_fields'; import { getDefaultFieldFilter } from './field_filter'; +import { IndexPatternField } from '../../../../../../data/common/index_patterns/fields'; -describe('group_fields', function () { - it('should group fields in selected, popular, unpopular group', function () { - const fields = [ - { - name: 'category', - type: 'string', - esTypes: ['text'], - count: 1, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - { - name: 'currency', - type: 'string', - esTypes: ['keyword'], - count: 0, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - { - name: 'customer_birth_date', - type: 'date', - esTypes: ['date'], - count: 0, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - ]; +const fields = [ + { + name: 'category', + type: 'string', + esTypes: ['text'], + count: 1, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'currency', + type: 'string', + esTypes: ['keyword'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'customer_birth_date', + type: 'date', + esTypes: ['date'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, +]; - const fieldCounts = { - category: 1, - currency: 1, - customer_birth_date: 1, - }; +const fieldCounts = { + category: 1, + currency: 1, + customer_birth_date: 1, +}; +describe('group_fields', function () { + it('should group fields in selected, popular, unpopular group', function () { const fieldFilterState = getDefaultFieldFilter(); - const actual = groupFields(fields as any, ['currency'], 5, fieldCounts, fieldFilterState); + const actual = groupFields( + fields as IndexPatternField[], + ['currency'], + 5, + fieldCounts, + fieldFilterState + ); expect(actual).toMatchInlineSnapshot(` Object { "popular": Array [ @@ -111,4 +118,34 @@ describe('group_fields', function () { } `); }); + + it('should sort selected fields by columns order ', function () { + const fieldFilterState = getDefaultFieldFilter(); + + const actual1 = groupFields( + fields as IndexPatternField[], + ['customer_birth_date', 'currency', 'unknown'], + 5, + fieldCounts, + fieldFilterState + ); + expect(actual1.selected.map((field) => field.name)).toEqual([ + 'customer_birth_date', + 'currency', + 'unknown', + ]); + + const actual2 = groupFields( + fields as IndexPatternField[], + ['currency', 'customer_birth_date', 'unknown'], + 5, + fieldCounts, + fieldFilterState + ); + expect(actual2.selected.map((field) => field.name)).toEqual([ + 'currency', + 'customer_birth_date', + 'unknown', + ]); + }); }); diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx index c6a06618900fd..c34becc97cb93 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx +++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx @@ -70,6 +70,15 @@ export function groupFields( result.unpopular.push(field); } } + // add columns, that are not part of the index pattern, to be removeable + for (const column of columns) { + if (!result.selected.find((field) => field.name === column)) { + result.selected.push({ name: column, displayName: column } as IndexPatternField); + } + } + result.selected.sort((a, b) => { + return columns.indexOf(a.name) - columns.indexOf(b.name); + }); return result; } diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index d0c3907d31242..e4a8ab7bc67ff 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -36,6 +36,7 @@ import { import { Container, Embeddable } from '../../../../embeddable/public'; import * as columnActions from '../angular/doc_table/actions/columns'; import searchTemplate from './search_template.html'; +import searchTemplateGrid from './search_template_datagrid.html'; import { ISearchEmbeddable, SearchInput, SearchOutput } from './types'; import { SortOrder } from '../angular/doc_table/components/table_header/helpers'; import { getSortForSearchSource } from '../angular/doc_table'; @@ -49,23 +50,29 @@ import { import { SEARCH_EMBEDDABLE_TYPE } from './constants'; import { SavedSearch } from '../..'; import { SAMPLE_SIZE_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; +import { DiscoverGridSettings } from '../components/discover_grid/types'; +import { DiscoverServices } from '../../build_services'; +import { ElasticSearchHit } from '../doc_views/doc_views_types'; import { getDefaultSort } from '../angular/doc_table/lib/get_default_sort'; interface SearchScope extends ng.IScope { columns?: string[]; + settings?: DiscoverGridSettings; description?: string; sort?: SortOrder[]; sharedItemTitle?: string; inspectorAdapters?: Adapters; setSortOrder?: (sortPair: SortOrder[]) => void; + setColumns?: (columns: string[]) => void; removeColumn?: (column: string) => void; addColumn?: (column: string) => void; moveColumn?: (column: string, index: number) => void; filter?: (field: IFieldType, value: string[], operator: string) => void; - hits?: any[]; + hits?: ElasticSearchHit[]; indexPattern?: IndexPattern; totalHitCount?: number; isLoading?: boolean; + showTimeCol?: boolean; } interface SearchEmbeddableConfig { @@ -77,6 +84,7 @@ interface SearchEmbeddableConfig { indexPatterns?: IndexPattern[]; editable: boolean; filterManager: FilterManager; + services: DiscoverServices; } export class SearchEmbeddable @@ -95,6 +103,7 @@ export class SearchEmbeddable public readonly type = SEARCH_EMBEDDABLE_TYPE; private filterManager: FilterManager; private abortController?: AbortController; + private services: DiscoverServices; private prevTimeRange?: TimeRange; private prevFilters?: Filter[]; @@ -111,6 +120,7 @@ export class SearchEmbeddable indexPatterns, editable, filterManager, + services, }: SearchEmbeddableConfig, initialInput: SearchInput, private readonly executeTriggerActions: UiActionsStart['executeTriggerActions'], @@ -128,7 +138,7 @@ export class SearchEmbeddable }, parent ); - + this.services = services; this.filterManager = filterManager; this.savedSearch = savedSearch; this.$rootScope = $rootScope; @@ -138,8 +148,8 @@ export class SearchEmbeddable }; this.initializeSearchScope(); - this.autoRefreshFetchSubscription = getServices() - .timefilter.getAutoRefreshFetch$() + this.autoRefreshFetchSubscription = this.services.timefilter + .getAutoRefreshFetch$() .subscribe(this.fetch); this.subscription = this.getUpdated$().subscribe(() => { @@ -167,7 +177,9 @@ export class SearchEmbeddable if (!this.searchScope) { throw new Error('Search scope not defined'); } - this.searchInstance = this.$compile(searchTemplate)(this.searchScope); + this.searchInstance = this.$compile( + this.services.uiSettings.get('doc_table:legacy', true) ? searchTemplate : searchTemplateGrid + )(this.searchScope); const rootNode = angular.element(domNode); rootNode.append(this.searchInstance); @@ -250,6 +262,15 @@ export class SearchEmbeddable this.updateInput({ columns }); }; + searchScope.setColumns = (columns: string[]) => { + this.updateInput({ columns }); + }; + + if (this.savedSearch.grid) { + searchScope.settings = this.savedSearch.grid; + } + searchScope.showTimeCol = !this.services.uiSettings.get('doc_table:hideTimeColumn', false); + searchScope.filter = async (field, value, operator) => { let filters = esFilters.generateFilters( this.filterManager, @@ -286,13 +307,13 @@ export class SearchEmbeddable if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); - searchSource.setField('size', getServices().uiSettings.get(SAMPLE_SIZE_SETTING)); + searchSource.setField('size', this.services.uiSettings.get(SAMPLE_SIZE_SETTING)); searchSource.setField( 'sort', getSortForSearchSource( this.searchScope.sort, this.searchScope.indexPattern, - getServices().uiSettings.get(SORT_DEFAULT_ORDER_SETTING) + this.services.uiSettings.get(SORT_DEFAULT_ORDER_SETTING) ) ); diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable_factory.ts b/src/plugins/discover/public/application/embeddable/search_embeddable_factory.ts index f61fa361f0c0e..d85476568201f 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable_factory.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable_factory.ts @@ -103,6 +103,7 @@ export class SearchEmbeddableFactory filterManager, editable: getServices().capabilities.discover.save as boolean, indexPatterns: indexPattern ? [indexPattern] : [], + services: getServices(), }, input, executeTriggerActions, diff --git a/src/plugins/discover/public/application/embeddable/search_template.html b/src/plugins/discover/public/application/embeddable/search_template.html index e188d230ea307..be2f5cceac080 100644 --- a/src/plugins/discover/public/application/embeddable/search_template.html +++ b/src/plugins/discover/public/application/embeddable/search_template.html @@ -1,20 +1,20 @@ diff --git a/src/plugins/discover/public/application/embeddable/search_template_datagrid.html b/src/plugins/discover/public/application/embeddable/search_template_datagrid.html new file mode 100644 index 0000000000000..6524783897f8f --- /dev/null +++ b/src/plugins/discover/public/application/embeddable/search_template_datagrid.html @@ -0,0 +1,19 @@ + diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts index 4dec1f75ba322..2ab1b93d6c37e 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts @@ -51,7 +51,7 @@ describe('getSharingData', () => { "searchRequest": Object { "body": Object { "_source": Object {}, - "fields": undefined, + "fields": Array [], "query": Object { "bool": Object { "filter": Array [], @@ -68,7 +68,9 @@ describe('getSharingData', () => { }, }, ], - "stored_fields": undefined, + "stored_fields": Array [ + "*", + ], }, "index": "the-index-pattern-title", }, diff --git a/src/plugins/discover/public/application/helpers/persist_saved_search.ts b/src/plugins/discover/public/application/helpers/persist_saved_search.ts index 8e956eff598f3..8ec2012b5843e 100644 --- a/src/plugins/discover/public/application/helpers/persist_saved_search.ts +++ b/src/plugins/discover/public/application/helpers/persist_saved_search.ts @@ -53,6 +53,9 @@ export async function persistSavedSearch( savedSearch.columns = state.columns || []; savedSearch.sort = (state.sort as SortOrder[]) || []; + if (state.grid) { + savedSearch.grid = state.grid; + } try { const id = await savedSearch.save(saveOptions); diff --git a/src/plugins/discover/public/get_inner_angular.ts b/src/plugins/discover/public/get_inner_angular.ts index 651a26cad755d..2ace65c31cc03 100644 --- a/src/plugins/discover/public/get_inner_angular.ts +++ b/src/plugins/discover/public/get_inner_angular.ts @@ -41,6 +41,7 @@ import { createTableRowDirective } from './application/angular/doc_table/compone import { createPagerFactory } from './application/angular/doc_table/lib/pager/pager_factory'; import { createInfiniteScrollDirective } from './application/angular/doc_table/infinite_scroll'; import { createDocViewerDirective } from './application/angular/doc_viewer'; +import { createDiscoverGridDirective } from './application/components/create_discover_grid_directive'; import { createRenderCompleteDirective } from './application/angular/directives/render_complete'; import { initAngularBootstrap, @@ -55,6 +56,8 @@ import { import { DiscoverStartPlugins } from './plugin'; import { getScopedHistory } from './kibana_services'; import { createDiscoverLegacyDirective } from './application/components/create_discover_legacy_directive'; +import { createDiscoverDirective } from './application/components/create_discover_directive'; + /** * returns the main inner angular module, it contains all the parts of Angular Discover * needs to render, so in the end the current 'kibana' angular module is no longer necessary @@ -136,7 +139,8 @@ export function initializeInnerAngularModule( .config(watchMultiDecorator) .run(registerListenEventListener) .directive('renderComplete', createRenderCompleteDirective) - .directive('discoverLegacy', createDiscoverLegacyDirective); + .directive('discoverLegacy', createDiscoverLegacyDirective) + .directive('discover', createDiscoverDirective); } function createLocalPromiseModule() { @@ -188,6 +192,7 @@ function createDocTableModule() { .directive('kbnTableRow', createTableRowDirective) .directive('toolBarPagerButtons', createToolBarPagerButtonsDirective) .directive('kbnInfiniteScroll', createInfiniteScrollDirective) + .directive('discoverGrid', createDiscoverGridDirective) .directive('docViewer', createDocViewerDirective) .directive('contextAppLegacy', createContextAppLegacy); } diff --git a/src/plugins/discover/public/saved_searches/_saved_search.ts b/src/plugins/discover/public/saved_searches/_saved_search.ts index 1ec4549f05d49..8a0ec128b4eb2 100644 --- a/src/plugins/discover/public/saved_searches/_saved_search.ts +++ b/src/plugins/discover/public/saved_searches/_saved_search.ts @@ -26,6 +26,7 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) { description: 'text', hits: 'integer', columns: 'keyword', + grid: 'object', sort: 'keyword', version: 'integer', }; @@ -45,6 +46,7 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) { description: 'text', hits: 'integer', columns: 'keyword', + grid: 'object', sort: 'keyword', version: 'integer', }, diff --git a/src/plugins/discover/public/saved_searches/types.ts b/src/plugins/discover/public/saved_searches/types.ts index d5e5dd765a364..7f6f1a2553d5e 100644 --- a/src/plugins/discover/public/saved_searches/types.ts +++ b/src/plugins/discover/public/saved_searches/types.ts @@ -19,6 +19,7 @@ import { SearchSource } from '../../../data/public'; import { SavedObjectSaveOpts } from '../../../saved_objects/public'; +import { DiscoverGridSettings } from '../application/components/discover_grid/types'; export type SortOrder = [string, string]; export interface SavedSearch { @@ -28,6 +29,7 @@ export interface SavedSearch { description?: string; columns: string[]; sort: SortOrder[]; + grid: DiscoverGridSettings; destroy: () => void; save: (saveOptions: SavedObjectSaveOpts) => Promise; lastSavedTitle?: string; diff --git a/src/plugins/discover/server/saved_objects/search.ts b/src/plugins/discover/server/saved_objects/search.ts index a6e42f956a025..d124a24b120fd 100644 --- a/src/plugins/discover/server/saved_objects/search.ts +++ b/src/plugins/discover/server/saved_objects/search.ts @@ -53,6 +53,7 @@ export const searchSavedObjectType: SavedObjectsType = { }, sort: { type: 'keyword', index: false, doc_values: false }, title: { type: 'text' }, + grid: { type: 'object', enabled: false }, version: { type: 'integer' }, }, }, diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index f45281ee62202..425928385e64a 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -33,6 +33,7 @@ import { CONTEXT_DEFAULT_SIZE_SETTING, CONTEXT_STEP_SETTING, CONTEXT_TIE_BREAKER_FIELDS_SETTING, + DOC_TABLE_LEGACY, MODIFY_COLUMNS_ON_SWITCH, } from '../common'; @@ -165,6 +166,23 @@ export const uiSettings: Record = { category: ['discover'], schema: schema.arrayOf(schema.string()), }, + [DOC_TABLE_LEGACY]: { + name: i18n.translate('discover.advancedSettings.docTableVersionName', { + defaultMessage: 'Use legacy table', + }), + value: true, + description: i18n.translate('discover.advancedSettings.docTableVersionDescription', { + defaultMessage: + 'Discover uses a new table layout that includes better data sorting, drag-and-drop columns, and a full screen ' + + 'view. Enable this option if you prefer to fall back to the legacy table.', + }), + category: ['discover'], + schema: schema.boolean(), + metric: { + type: METRIC_TYPE.CLICK, + name: 'discover:useLegacyDataGrid', + }, + }, [MODIFY_COLUMNS_ON_SWITCH]: { name: i18n.translate('discover.advancedSettings.discover.modifyColumnsOnSwitchTitle', { defaultMessage: 'Modify columns when changing index patterns', diff --git a/test/functional/apps/dashboard/embeddable_data_grid.ts b/test/functional/apps/dashboard/embeddable_data_grid.ts new file mode 100644 index 0000000000000..067536ab7aa93 --- /dev/null +++ b/test/functional/apps/dashboard/embeddable_data_grid.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const dashboardAddPanel = getService('dashboardAddPanel'); + const filterBar = getService('filterBar'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const find = getService('find'); + const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'timePicker', 'discover']); + + describe('dashboard embeddable data grid', () => { + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.loadIfNeeded('dashboard/current/data'); + await esArchiver.loadIfNeeded('dashboard/current/kibana'); + await kibanaServer.uiSettings.replace({ + defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', + 'doc_table:legacy': false, + }); + await PageObjects.common.navigateToApp('dashboard'); + await filterBar.ensureFieldEditorModalIsClosed(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.timePicker.setDefaultDataRange(); + }); + + describe('saved search filters', function () { + it('are added when a cell filter is clicked', async function () { + await dashboardAddPanel.addSavedSearch('Rendering-Test:-saved-search'); + await find.clickByCssSelector(`[role="gridcell"]:nth-child(2)`); + await find.clickByCssSelector(`[data-test-subj="filterOutButton"]`); + await PageObjects.header.waitUntilLoadingHasFinished(); + await find.clickByCssSelector(`[role="gridcell"]:nth-child(2)`); + await find.clickByCssSelector(`[data-test-subj="filterForButton"]`); + const filterCount = await filterBar.getFilterCount(); + expect(filterCount).to.equal(2); + }); + }); + }); +} diff --git a/test/functional/apps/dashboard/index.ts b/test/functional/apps/dashboard/index.ts index 6fb5f874022a0..43ad1aad5de00 100644 --- a/test/functional/apps/dashboard/index.ts +++ b/test/functional/apps/dashboard/index.ts @@ -54,6 +54,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./empty_dashboard')); loadTestFile(require.resolve('./url_field_formatter')); loadTestFile(require.resolve('./embeddable_rendering')); + loadTestFile(require.resolve('./embeddable_data_grid')); loadTestFile(require.resolve('./create_and_add_embeddables')); loadTestFile(require.resolve('./edit_embeddable_redirects')); loadTestFile(require.resolve('./edit_visualizations')); diff --git a/test/functional/apps/discover/_data_grid.ts b/test/functional/apps/discover/_data_grid.ts new file mode 100644 index 0000000000000..8f62e03518253 --- /dev/null +++ b/test/functional/apps/discover/_data_grid.ts @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; + +export default function ({ + getService, + getPageObjects, +}: { + getService: (service: string) => any; + getPageObjects: (pageObjects: string[]) => any; +}) { + describe('discover data grid tests', function describeDiscoverDataGrid() { + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['common', 'discover', 'timePicker']); + const kibanaServer = getService('kibanaServer'); + const defaultSettings = { defaultIndex: 'logstash-*', 'doc_table:legacy': false }; + const testSubjects = getService('testSubjects'); + + before(async function () { + await esArchiver.load('discover'); + await esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.replace(defaultSettings); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + }); + + after(async function () { + await kibanaServer.uiSettings.replace({ 'doc_table:legacy': true }); + }); + + it('can add fields to the table', async function () { + const getTitles = async () => + (await testSubjects.getVisibleText('dataGridHeader')).replace(/\s|\r?\n|\r/g, ' '); + + expect(await getTitles()).to.be('Time (@timestamp) _source'); + + await PageObjects.discover.clickFieldListItemAdd('bytes'); + expect(await getTitles()).to.be('Time (@timestamp) bytes'); + + await PageObjects.discover.clickFieldListItemAdd('agent'); + expect(await getTitles()).to.be('Time (@timestamp) bytes agent'); + + await PageObjects.discover.clickFieldListItemAdd('bytes'); + expect(await getTitles()).to.be('Time (@timestamp) agent'); + + await PageObjects.discover.clickFieldListItemAdd('agent'); + expect(await getTitles()).to.be('Time (@timestamp) _source'); + }); + }); +} diff --git a/test/functional/apps/discover/_data_grid_context.ts b/test/functional/apps/discover/_data_grid_context.ts new file mode 100644 index 0000000000000..6821b9c69cf7e --- /dev/null +++ b/test/functional/apps/discover/_data_grid_context.ts @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const TEST_COLUMN_NAMES = ['@message']; +const TEST_FILTER_COLUMN_NAMES = [ + ['extension', 'jpg'], + ['geo.src', 'IN'], +]; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const retry = getService('retry'); + const filterBar = getService('filterBar'); + const dataGrid = getService('dataGrid'); + const docTable = getService('docTable'); + const PageObjects = getPageObjects(['common', 'discover', 'timePicker', 'settings']); + const defaultSettings = { defaultIndex: 'logstash-*', 'doc_table:legacy': false }; + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + + describe('discover data grid context tests', () => { + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update(defaultSettings); + await PageObjects.common.navigateToApp('discover'); + + for (const columnName of TEST_COLUMN_NAMES) { + await PageObjects.discover.clickFieldListItemAdd(columnName); + } + + for (const [columnName, value] of TEST_FILTER_COLUMN_NAMES) { + await PageObjects.discover.clickFieldListItem(columnName); + await PageObjects.discover.clickFieldListPlusFilter(columnName, value); + } + }); + after(async () => { + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + }); + + it('should open the context view with the selected document as anchor', async () => { + // check the anchor timestamp in the context view + await retry.waitFor('selected document timestamp matches anchor timestamp ', async () => { + // get the timestamp of the first row + const discoverFields = await dataGrid.getFields(); + const firstTimestamp = discoverFields[0][0]; + + // navigate to the context view + await dataGrid.clickRowToggle({ rowIndex: 0 }); + const rowActions = await dataGrid.getRowActions({ rowIndex: 0 }); + await rowActions[1].click(); + // entering the context view (contains the legacy type) + const contextFields = await docTable.getFields(); + const anchorTimestamp = contextFields[0][0]; + return anchorTimestamp === firstTimestamp; + }); + }); + + it('should open the context view with the same columns', async () => { + const columnNames = await docTable.getHeaderFields(); + expect(columnNames).to.eql(['Time', ...TEST_COLUMN_NAMES]); + }); + + it('should open the context view with the filters disabled', async () => { + let disabledFilterCounter = 0; + for (const [columnName, value] of TEST_FILTER_COLUMN_NAMES) { + if (await filterBar.hasFilter(columnName, value, false)) { + disabledFilterCounter++; + } + } + expect(disabledFilterCounter).to.be(TEST_FILTER_COLUMN_NAMES.length); + }); + }); +} diff --git a/test/functional/apps/discover/_data_grid_doc_navigation.ts b/test/functional/apps/discover/_data_grid_doc_navigation.ts new file mode 100644 index 0000000000000..92d9893cab0b6 --- /dev/null +++ b/test/functional/apps/discover/_data_grid_doc_navigation.ts @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const filterBar = getService('filterBar'); + const dataGrid = getService('dataGrid'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'discover', 'timePicker', 'context']); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const kibanaServer = getService('kibanaServer'); + const defaultSettings = { defaultIndex: 'logstash-*', 'doc_table:legacy': false }; + + describe('discover data grid doc link', function () { + beforeEach(async function () { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.loadIfNeeded('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update(defaultSettings); + await PageObjects.common.navigateToApp('discover'); + }); + + it('should open the doc view of the selected document', async function () { + // navigate to the doc view + await dataGrid.clickRowToggle({ rowIndex: 0 }); + + // click the open action + await retry.try(async () => { + const rowActions = await dataGrid.getRowActions({ rowIndex: 0 }); + if (!rowActions.length) { + throw new Error('row actions empty, trying again'); + } + await rowActions[0].click(); + }); + + const hasDocHit = await testSubjects.exists('doc-hit'); + expect(hasDocHit).to.be(true); + }); + + it('add filter should create an exists filter if value is null (#7189)', async function () { + await PageObjects.discover.waitUntilSearchingHasFinished(); + // Filter special document + await filterBar.addFilter('agent', 'is', 'Missing/Fields'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + await retry.try(async () => { + // navigate to the doc view + await dataGrid.clickRowToggle({ rowIndex: 0 }); + + const details = await dataGrid.getDetailsRow(); + await dataGrid.addInclusiveFilter(details, 'referer'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + const hasInclusiveFilter = await filterBar.hasFilter( + 'referer', + 'exists', + true, + false, + true + ); + expect(hasInclusiveFilter).to.be(true); + + await dataGrid.clickRowToggle({ rowIndex: 0 }); + const detailsExcluding = await dataGrid.getDetailsRow(); + await dataGrid.removeInclusiveFilter(detailsExcluding, 'referer'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const hasExcludeFilter = await filterBar.hasFilter('referer', 'exists', true, false, false); + expect(hasExcludeFilter).to.be(true); + }); + }); + }); +} diff --git a/test/functional/apps/discover/_data_grid_doc_table.ts b/test/functional/apps/discover/_data_grid_doc_table.ts new file mode 100644 index 0000000000000..1224823abf048 --- /dev/null +++ b/test/functional/apps/discover/_data_grid_doc_table.ts @@ -0,0 +1,132 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const dataGrid = getService('dataGrid'); + const log = getService('log'); + const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); + const defaultSettings = { + defaultIndex: 'logstash-*', + 'doc_table:legacy': false, + }; + + describe('discover data grid doc table', function describeIndexTests() { + const defaultRowsLimit = 25; + + before(async function () { + log.debug('load kibana index with default index pattern'); + await esArchiver.load('discover'); + await esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.replace(defaultSettings); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await PageObjects.common.navigateToApp('discover'); + }); + + it('should show the first 50 rows by default', async function () { + // with the default range the number of hits is ~14000 + const rows = await dataGrid.getDocTableRows(); + expect(rows.length).to.be(defaultRowsLimit); + }); + + it('should refresh the table content when changing time window', async function () { + const initialRows = await dataGrid.getDocTableRows(); + + const fromTime = 'Sep 20, 2015 @ 23:00:00.000'; + const toTime = 'Sep 20, 2015 @ 23:14:00.000'; + + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + const finalRows = await PageObjects.discover.getDocTableRows(); + expect(finalRows.length).to.be.below(initialRows.length); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + }); + + describe('expand a document row', function () { + const rowToInspect = 1; + + it('should expand the detail row when the toggle arrow is clicked', async function () { + await retry.try(async function () { + await dataGrid.clickRowToggle({ isAnchorRow: false, rowIndex: rowToInspect - 1 }); + const detailsEl = await dataGrid.getDetailsRows(); + const defaultMessageEl = await detailsEl[0].findByTestSubject('docTableRowDetailsTitle'); + expect(defaultMessageEl).to.be.ok(); + await dataGrid.closeFlyout(); + }); + }); + + it('should show the detail panel actions', async function () { + await retry.try(async function () { + await dataGrid.clickRowToggle({ isAnchorRow: false, rowIndex: rowToInspect - 1 }); + const [surroundingActionEl, singleActionEl] = await dataGrid.getRowActions({ + isAnchorRow: false, + rowIndex: rowToInspect - 1, + }); + expect(surroundingActionEl).to.be.ok(); + expect(singleActionEl).to.be.ok(); + await dataGrid.closeFlyout(); + }); + }); + }); + + describe('add and remove columns', function () { + const extraColumns = ['phpmemory', 'ip']; + + afterEach(async function () { + for (const column of extraColumns) { + await PageObjects.discover.clickFieldListItemRemove(column); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + }); + + it('should add more columns to the table', async function () { + for (const column of extraColumns) { + await PageObjects.discover.clearFieldSearchInput(); + await PageObjects.discover.findFieldByName(column); + await PageObjects.discover.clickFieldListItemAdd(column); + await PageObjects.header.waitUntilLoadingHasFinished(); + // test the header now + const header = await dataGrid.getHeaderFields(); + expect(header.join(' ')).to.have.string(column); + } + }); + + it('should remove columns from the table', async function () { + for (const column of extraColumns) { + await PageObjects.discover.clearFieldSearchInput(); + await PageObjects.discover.findFieldByName(column); + await PageObjects.discover.clickFieldListItemAdd(column); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + // remove the second column + await PageObjects.discover.clickFieldListItemAdd(extraColumns[1]); + await PageObjects.header.waitUntilLoadingHasFinished(); + // test that the second column is no longer there + const header = await dataGrid.getHeaderFields(); + expect(header.join(' ')).to.not.have.string(extraColumns[1]); + }); + }); + }); +} diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts new file mode 100644 index 0000000000000..8224f59f7fabf --- /dev/null +++ b/test/functional/apps/discover/_data_grid_field_data.ts @@ -0,0 +1,99 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const toasts = getService('toasts'); + const queryBar = getService('queryBar'); + const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); + const defaultSettings = { defaultIndex: 'logstash-*', 'doc_table:legacy': false }; + const dataGrid = getService('dataGrid'); + + describe('discover data grid field data tests', function describeIndexTests() { + this.tags('includeFirefox'); + before(async function () { + await esArchiver.load('discover'); + await esArchiver.loadIfNeeded('logstash_functional'); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update(defaultSettings); + await PageObjects.common.navigateToApp('discover'); + }); + describe('field data', function () { + it('search php should show the correct hit count', async function () { + const expectedHitCount = '445'; + await retry.try(async function () { + await queryBar.setQuery('php'); + await queryBar.submitQuery(); + const hitCount = await PageObjects.discover.getHitCount(); + expect(hitCount).to.be(expectedHitCount); + }); + }); + + it('the search term should be highlighted in the field data', async function () { + // marks is the style that highlights the text in yellow + const marks = await PageObjects.discover.getMarks(); + expect(marks.length).to.be(25); + expect(marks.indexOf('php')).to.be(0); + }); + + it('search type:apache should show the correct hit count', async function () { + const expectedHitCount = '11,156'; + await queryBar.setQuery('type:apache'); + await queryBar.submitQuery(); + await retry.try(async function tryingForTime() { + const hitCount = await PageObjects.discover.getHitCount(); + expect(hitCount).to.be(expectedHitCount); + }); + }); + + it('doc view should show Time and _source columns', async function () { + const expectedHeader = 'Time (@timestamp) _source'; + const DocHeader = await dataGrid.getHeaderFields(); + expect(DocHeader.join(' ')).to.be(expectedHeader); + }); + + it('doc view should sort ascending', async function () { + const expectedTimeStamp = 'Sep 20, 2015 @ 00:00:00.000'; + await dataGrid.clickDocSortAsc(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + await retry.try(async function tryingForTime() { + const rowData = await dataGrid.getFields(); + expect(rowData[0][0].startsWith(expectedTimeStamp)).to.be.ok(); + }); + }); + + it('a bad syntax query should show an error message', async function () { + const expectedError = + 'Expected ":", "<", "<=", ">", ">=", AND, OR, end of input, ' + + 'whitespace but "(" found.'; + await queryBar.setQuery('xxx(yyy))'); + await queryBar.submitQuery(); + const { message } = await toasts.getErrorToast(); + expect(message).to.contain(expectedError); + await toasts.dismissToast(); + }); + }); + }); +} diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index 20fda144b338e..40a6ab31f7d4c 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -131,13 +131,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should add more columns to the table', async function () { - const [column] = extraColumns; - await PageObjects.discover.findFieldByName(column); - log.debug(`add a ${column} column`); - await PageObjects.discover.clickFieldListItemAdd(column); - await PageObjects.header.waitUntilLoadingHasFinished(); - // test the header now - expect(await PageObjects.discover.getDocHeader()).to.have.string(column); + for (const column of extraColumns) { + await PageObjects.discover.clearFieldSearchInput(); + await PageObjects.discover.findFieldByName(column); + await PageObjects.discover.clickFieldListItemAdd(column); + await PageObjects.header.waitUntilLoadingHasFinished(); + // test the header now + expect(await PageObjects.discover.getDocHeader()).to.have.string(column); + } }); it('should remove columns from the table', async function () { diff --git a/test/functional/apps/discover/index.ts b/test/functional/apps/discover/index.ts index c13529b7d1b43..450049af66abf 100644 --- a/test/functional/apps/discover/index.ts +++ b/test/functional/apps/discover/index.ts @@ -51,5 +51,10 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_date_nanos')); loadTestFile(require.resolve('./_date_nanos_mixed')); loadTestFile(require.resolve('./_indexpattern_without_timefield')); + loadTestFile(require.resolve('./_data_grid')); + loadTestFile(require.resolve('./_data_grid_context')); + loadTestFile(require.resolve('./_data_grid_field_data')); + loadTestFile(require.resolve('./_data_grid_doc_navigation')); + loadTestFile(require.resolve('./_data_grid_doc_table')); }); } diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 209e30d23ca3c..c538d8156103c 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -24,10 +24,15 @@ interface TabbedGridData { columns: string[]; rows: string[][]; } +interface SelectOptions { + isAnchorRow?: boolean; + rowIndex: number; +} -export function DataGridProvider({ getService }: FtrProviderContext) { +export function DataGridProvider({ getService, getPageObjects }: FtrProviderContext) { const find = getService('find'); const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'header']); class DataGrid { async getDataGridTableData(): Promise { @@ -103,6 +108,137 @@ export function DataGridProvider({ getService }: FtrProviderContext) { [data-test-subj="dataGridRowCell"]:nth-of-type(${columnIndex})` ); } + public async getFields() { + const rows = await find.allByCssSelector('.euiDataGridRow'); + + const result = []; + for (const row of rows) { + const cells = await row.findAllByClassName('euiDataGridRowCell__truncate'); + const cellsText = []; + let cellIdx = 0; + for (const cell of cells) { + if (cellIdx > 0) { + cellsText.push(await cell.getVisibleText()); + } + cellIdx++; + } + result.push(cellsText); + } + return result; + } + + public async getTable(selector: string = 'docTable') { + return await testSubjects.find(selector); + } + + public async getBodyRows(): Promise { + const table = await this.getTable(); + return await table.findAllByTestSubject('dataGridRow'); + } + + public async getDocTableRows() { + const table = await this.getTable(); + return await table.findAllByTestSubject('dataGridRow'); + } + + public async getAnchorRow(): Promise { + const table = await this.getTable(); + return await table.findByTestSubject('~docTableAnchorRow'); + } + + public async getRow(options: SelectOptions): Promise { + return options.isAnchorRow + ? await this.getAnchorRow() + : (await this.getBodyRows())[options.rowIndex]; + } + + public async clickRowToggle( + options: SelectOptions = { isAnchorRow: false, rowIndex: 0 } + ): Promise { + const row = await this.getRow(options); + const toggle = await row.findByTestSubject('~docTableExpandToggleColumn'); + await toggle.click(); + } + + public async getDetailsRows(): Promise { + return await testSubjects.findAll('docTableDetailsFlyout'); + } + + public async closeFlyout() { + await testSubjects.click('euiFlyoutCloseButton'); + } + + public async getHeaderFields(): Promise { + const result = await find.allByCssSelector('.euiDataGridHeaderCell__content'); + const textArr = []; + let idx = 0; + for (const cell of result) { + if (idx > 0) { + textArr.push(await cell.getVisibleText()); + } + idx++; + } + return Promise.resolve(textArr); + } + + public async getRowActions( + options: SelectOptions = { isAnchorRow: false, rowIndex: 0 } + ): Promise { + const detailsRow = (await this.getDetailsRows())[options.rowIndex]; + return await detailsRow.findAllByTestSubject('~docTableRowAction'); + } + + public async clickDocSortAsc() { + await find.clickByCssSelector('.euiDataGridHeaderCell__button'); + await find.clickByButtonText('Sort New-Old'); + } + + public async clickDocSortDesc() { + await find.clickByCssSelector('.euiDataGridHeaderCell__button'); + await find.clickByButtonText('Sort Old-New'); + } + public async getDetailsRow(): Promise { + const detailRows = await this.getDetailsRows(); + return detailRows[0]; + } + public async addInclusiveFilter( + detailsRow: WebElementWrapper, + fieldName: string + ): Promise { + const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); + const addInclusiveFilterButton = await this.getAddInclusiveFilterButton(tableDocViewRow); + await addInclusiveFilterButton.click(); + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } + + public async getAddInclusiveFilterButton( + tableDocViewRow: WebElementWrapper + ): Promise { + return await tableDocViewRow.findByTestSubject(`~addInclusiveFilterButton`); + } + + public async getTableDocViewRow( + detailsRow: WebElementWrapper, + fieldName: string + ): Promise { + return await detailsRow.findByTestSubject(`~tableDocViewRow-${fieldName}`); + } + + public async getRemoveInclusiveFilterButton( + tableDocViewRow: WebElementWrapper + ): Promise { + return await tableDocViewRow.findByTestSubject(`~removeInclusiveFilterButton`); + } + + public async removeInclusiveFilter( + detailsRow: WebElementWrapper, + fieldName: string + ): Promise { + const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); + const addInclusiveFilterButton = await this.getRemoveInclusiveFilterButton(tableDocViewRow); + await addInclusiveFilterButton.click(); + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } } return new DataGrid(); From 15b80dd771511c8a953740b7a32d83946a4f07e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 23 Dec 2020 09:31:01 +0100 Subject: [PATCH 36/72] [APM] Filter out service nodes if there are no metrics (#86639) * filtering out metrics without service.node.name * filtering out metrics without service.node.name * addressing pr comments * fix TS issue --- .../apm/server/lib/service_nodes/index.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/service_nodes/index.ts b/x-pack/plugins/apm/server/lib/service_nodes/index.ts index d5e29532e3d7b..ca58a1b0e7126 100644 --- a/x-pack/plugins/apm/server/lib/service_nodes/index.ts +++ b/x-pack/plugins/apm/server/lib/service_nodes/index.ts @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup, SetupTimeRange } from '../helpers/setup_request'; -import { getServiceNodesProjection } from '../../projections/service_nodes'; -import { mergeProjection } from '../../projections/util/merge_projection'; -import { SERVICE_NODE_NAME_MISSING } from '../../../common/service_nodes'; import { - METRIC_PROCESS_CPU_PERCENT, - METRIC_JAVA_THREAD_COUNT, METRIC_JAVA_HEAP_MEMORY_USED, METRIC_JAVA_NON_HEAP_MEMORY_USED, + METRIC_JAVA_THREAD_COUNT, + METRIC_PROCESS_CPU_PERCENT, } from '../../../common/elasticsearch_fieldnames'; +import { SERVICE_NODE_NAME_MISSING } from '../../../common/service_nodes'; +import { getServiceNodesProjection } from '../../projections/service_nodes'; +import { mergeProjection } from '../../projections/util/merge_projection'; +import { Setup, SetupTimeRange } from '../helpers/setup_request'; const getServiceNodes = async ({ setup, @@ -68,15 +68,21 @@ const getServiceNodes = async ({ return []; } - return response.aggregations.nodes.buckets.map((bucket) => { - return { + return response.aggregations.nodes.buckets + .map((bucket) => ({ name: bucket.key as string, cpu: bucket.cpu.value, heapMemory: bucket.heapMemory.value, nonHeapMemory: bucket.nonHeapMemory.value, threadCount: bucket.threadCount.value, - }; - }); + })) + .filter( + (item) => + item.cpu !== null || + item.heapMemory !== null || + item.nonHeapMemory !== null || + item.threadCount != null + ); }; export { getServiceNodes }; From 0d3daa564f0ced3629fe0d3bbd202f7bc67f633f Mon Sep 17 00:00:00 2001 From: Katrin Freihofner Date: Wed, 23 Dec 2020 09:38:35 +0100 Subject: [PATCH 37/72] [Logs UI] removes unnecessary panel in categories tabL (#86769) --- .../page_results_content.tsx | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx index 98367335d9c2d..6fc9ce3d8983e 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx @@ -178,21 +178,19 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent - - - - - - - - + + + + + + Date: Wed, 23 Dec 2020 13:00:52 +0100 Subject: [PATCH 38/72] [APM] "View job" link from latency charts leads to a malfunctioning page (#86788) * fixing ML links * fixing ML links --- .../Settings/anomaly_detection/jobs_list.tsx | 24 +++---- .../MLExplorerLink.test.tsx | 44 +++++++++++++ .../MachineLearningLinks/MLExplorerLink.tsx | 63 +++++++++++++++++++ .../charts/transaction_charts/ml_header.tsx | 4 +- 4 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.test.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx index acc2550930b8e..b185685f0720a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx @@ -4,26 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { - EuiPanel, - EuiTitle, - EuiText, - EuiSpacer, EuiButton, EuiFlexGroup, EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { getEnvironmentLabel } from '../../../../../common/environment_filter_values'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; -import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable'; -import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; -import { MLSingleMetricLink } from '../../../shared/Links/MachineLearningLinks/MLSingleMetricLink'; +import { MLExplorerLink } from '../../../shared/Links/MachineLearningLinks/MLExplorerLink'; import { MLManageJobsLink } from '../../../shared/Links/MachineLearningLinks/MLManageJobsLink'; -import { getEnvironmentLabel } from '../../../../../common/environment_filter_values'; -import { LegacyJobsCallout } from './legacy_jobs_callout'; +import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; +import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable'; import { AnomalyDetectionApiResponse } from './index'; +import { LegacyJobsCallout } from './legacy_jobs_callout'; type Jobs = AnomalyDetectionApiResponse['jobs']; @@ -44,14 +44,14 @@ const columns: Array> = [ { defaultMessage: 'Action' } ), render: (jobId: string) => ( - + {i18n.translate( 'xpack.apm.settings.anomalyDetection.jobList.mlJobLinkText', { defaultMessage: 'View job in ML', } )} - + ), }, ]; diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.test.tsx new file mode 100644 index 0000000000000..3f02ed082f564 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Location } from 'history'; +import React from 'react'; +import { getRenderedHref } from '../../../../utils/testHelpers'; +import { MLExplorerLink } from './MLExplorerLink'; + +describe('MLExplorerLink', () => { + it('should produce the correct URL with jobId', async () => { + const href = await getRenderedHref( + () => ( + + ), + { + search: + '?rangeFrom=now/w&rangeTo=now-4h&refreshPaused=true&refreshInterval=0', + } as Location + ); + + expect(href).toMatchInlineSnapshot( + `"/app/ml/explorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now%2Fw,to:now-4h))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:()))"` + ); + }); + + it('correctly encodes time range values', async () => { + const href = await getRenderedHref( + () => ( + + ), + { + search: + '?rangeFrom=2020-07-29T17:27:29.000Z&rangeTo=2020-07-29T18:45:00.000Z&refreshInterval=10000&refreshPaused=true', + } as Location + ); + + expect(href).toMatchInlineSnapshot( + `"/app/ml/explorer?_g=(ml:(jobIds:!(apm-production-485b-high_mean_transaction_duration)),refreshInterval:(pause:!t,value:10000),time:(from:'2020-07-29T17:27:29.000Z',to:'2020-07-29T18:45:00.000Z'))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:()))"` + ); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.tsx new file mode 100644 index 0000000000000..ca9eb063bd090 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLExplorerLink.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { ReactNode } from 'react'; +import { EuiLink } from '@elastic/eui'; +import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/common'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useMlHref, ML_PAGES } from '../../../../../../ml/public'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { TimePickerRefreshInterval } from '../../DatePicker/typings'; + +interface Props { + children?: ReactNode; + jobId: string; + external?: boolean; +} + +export function MLExplorerLink({ jobId, external, children }: Props) { + const href = useExplorerHref({ jobId }); + + return ( + + ); +} + +export function useExplorerHref({ jobId }: { jobId: string }) { + const { + core, + plugins: { ml }, + } = useApmPluginContext(); + const { urlParams } = useUrlParams(); + + const timePickerRefreshIntervalDefaults = core.uiSettings.get( + UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS + ); + + const { + // hardcoding a custom default of 1 hour since the default kibana timerange of 15 minutes is shorter than the ML interval + rangeFrom = 'now-1h', + rangeTo = 'now', + refreshInterval = timePickerRefreshIntervalDefaults.value, + refreshPaused = timePickerRefreshIntervalDefaults.pause, + } = urlParams; + + const href = useMlHref(ml, core.http.basePath.get(), { + page: ML_PAGES.ANOMALY_EXPLORER, + pageState: { + jobIds: [jobId], + timeRange: { from: rangeFrom, to: rangeTo }, + refreshInterval: { pause: refreshPaused, value: refreshInterval }, + }, + }); + + return href; +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx index d125af70268cb..33dcbf02ccda7 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx @@ -10,6 +10,7 @@ import { isEmpty } from 'lodash'; import React from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { MLSingleMetricLink } from '../../Links/MachineLearningLinks/MLSingleMetricLink'; @@ -33,12 +34,13 @@ const ShiftedEuiText = styled(EuiText)` export function MLHeader({ hasValidMlLicense, mlJobId }: Props) { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); + const { transactionType } = useApmServiceContext(); if (!hasValidMlLicense || !mlJobId) { return null; } - const { kuery, transactionType } = urlParams; + const { kuery } = urlParams; const hasKuery = !isEmpty(kuery); const icon = hasKuery ? ( From d4d70f22cf76bf7414c357d4bd145ebf917fabf3 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 23 Dec 2020 14:00:59 +0100 Subject: [PATCH 39/72] [ML] Enforce pause when it's set to false with 0 refresh interval (#86805) * [ML] Enforce pause when it's set to false with 0 refresh interval * [ML] add mocks, fix unit tests --- .../select_interval/select_interval.tsx | 2 +- .../date_picker_wrapper.test.tsx | 93 +++++++------------ .../date_picker_wrapper.tsx | 43 ++++----- .../routes/timeseriesexplorer.test.tsx | 57 +++++++++--- .../routing/routes/timeseriesexplorer.tsx | 10 +- .../__mocks__/index.ts | 7 ++ .../use_timeseriesexplorer_url_state.ts | 9 ++ .../timeseriesexplorer.d.ts | 9 +- .../validate_job_selection.ts | 7 +- .../application/util/__mocks__/url_state.tsx | 27 ++++++ .../ml/public/application/util/url_state.tsx | 4 +- 11 files changed, 162 insertions(+), 106 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/services/toast_notification_service/__mocks__/index.ts create mode 100644 x-pack/plugins/ml/public/application/timeseriesexplorer/hooks/__mocks__/use_timeseriesexplorer_url_state.ts create mode 100644 x-pack/plugins/ml/public/application/util/__mocks__/url_state.tsx diff --git a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx index 059ab48daa27e..0fc1d458399dd 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx @@ -51,7 +51,7 @@ function optionValueToInterval(value: string) { return interval; } -const TABLE_INTERVAL_DEFAULT = optionValueToInterval('auto'); +export const TABLE_INTERVAL_DEFAULT = optionValueToInterval('auto'); export const useTableInterval = (): [TableInterval, (v: TableInterval) => void] => { return usePageUrlState('mlSelectInterval', TABLE_INTERVAL_DEFAULT); diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx index e2aa13f6019ed..b882531eb75b0 100644 --- a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx +++ b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx @@ -5,15 +5,31 @@ */ import { mount } from 'enzyme'; +import { render } from '@testing-library/react'; import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; import { EuiSuperDatePicker } from '@elastic/eui'; +import { useUrlState } from '../../../util/url_state'; import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service'; import { DatePickerWrapper } from './date_picker_wrapper'; +jest.mock('@elastic/eui', () => { + const EuiSuperDatePickerMock = jest.fn(() => { + return null; + }); + return { EuiSuperDatePicker: EuiSuperDatePickerMock }; +}); + +jest.mock('../../../util/url_state', () => { + return { + useUrlState: jest.fn(() => { + return [{ refreshInterval: { value: 0, pause: true } }, jest.fn()]; + }), + }; +}); + jest.mock('../../../contexts/kibana', () => ({ useMlKibana: () => { return { @@ -25,9 +41,11 @@ jest.mock('../../../contexts/kibana', () => ({ timefilter: { getRefreshInterval: jest.fn(), setRefreshInterval: jest.fn(), - getTime: jest.fn(), - isAutoRefreshSelectorEnabled: jest.fn(), - isTimeRangeSelectorEnabled: jest.fn(), + getTime: jest.fn(() => { + return { from: '', to: '' }; + }), + isAutoRefreshSelectorEnabled: jest.fn(() => true), + isTimeRangeSelectorEnabled: jest.fn(() => true), getRefreshIntervalUpdate$: jest.fn(), getTimeUpdate$: jest.fn(), getEnabledUpdated$: jest.fn(), @@ -41,11 +59,12 @@ jest.mock('../../../contexts/kibana', () => ({ }, })); -const noop = () => {}; +const MockedEuiSuperDatePicker = EuiSuperDatePicker as jest.MockedClass; describe('Navigation Menu: ', () => { beforeEach(() => { jest.useFakeTimers(); + MockedEuiSuperDatePicker.mockClear(); }); afterEach(() => { @@ -56,66 +75,22 @@ describe('Navigation Menu: ', () => { const refreshListener = jest.fn(); const refreshSubscription = mlTimefilterRefresh$.subscribe(refreshListener); - const wrapper = mount( - - - - ); + const wrapper = mount(); expect(wrapper.find(DatePickerWrapper)).toHaveLength(1); expect(refreshListener).toBeCalledTimes(0); refreshSubscription.unsubscribe(); }); - // The following tests are written against EuiSuperDatePicker - // instead of DatePickerWrapper. DatePickerWrapper uses hooks and we cannot write tests - // with async hook updates yet until React 16.9 is available. - test('Listen for consecutive super date picker refreshs.', async () => { - const onRefresh = jest.fn(); - - const componentRefresh = mount( - - ); - - const instanceRefresh = componentRefresh.instance(); - - jest.advanceTimersByTime(10); - // @ts-ignore - await instanceRefresh.asyncInterval.__pendingFn; - jest.advanceTimersByTime(10); - // @ts-ignore - await instanceRefresh.asyncInterval.__pendingFn; - - expect(onRefresh).toBeCalledTimes(2); - }); + test('should not allow disabled pause with 0 refresh interval', () => { + // arrange + (useUrlState as jest.Mock).mockReturnValue([{ refreshInterval: { pause: false, value: 0 } }]); + + // act + render(); - test('Switching refresh interval to pause should stop onRefresh being called.', async () => { - const onRefresh = jest.fn(); - - const componentRefresh = mount( - - ); - - const instanceRefresh = componentRefresh.instance(); - - jest.advanceTimersByTime(10); - // @ts-ignore - await instanceRefresh.asyncInterval.__pendingFn; - componentRefresh.setProps({ isPaused: true, refreshInterval: 0 }); - jest.advanceTimersByTime(10); - // @ts-ignore - await instanceRefresh.asyncInterval.__pendingFn; - - expect(onRefresh).toBeCalledTimes(1); + // assert + const calledWith = MockedEuiSuperDatePicker.mock.calls[0][0]; + expect(calledWith.isPaused).toBe(true); }); }); diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx index a4dc78ea53a77..dc046241f82b9 100644 --- a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx +++ b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment, useCallback, useEffect, useState } from 'react'; +import React, { FC, useCallback, useEffect, useState } from 'react'; import { Subscription } from 'rxjs'; import { debounce } from 'lodash'; @@ -122,24 +122,25 @@ export const DatePickerWrapper: FC = () => { setRefreshInterval({ pause, value }); } - return ( - - {(isAutoRefreshSelectorEnabled || isTimeRangeSelectorEnabled) && ( -
    - -
    - )} -
    - ); + /** + * Enforce pause when it's set to false with 0 refresh interval. + */ + const isPaused = refreshInterval.pause || (!refreshInterval.pause && !refreshInterval.value); + + return isAutoRefreshSelectorEnabled || isTimeRangeSelectorEnabled ? ( +
    + +
    + ) : null; }; diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx index b60a265560455..97ea27c5fe40a 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx @@ -5,12 +5,44 @@ */ import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; import { render } from '@testing-library/react'; - import { I18nProvider } from '@kbn/i18n/react'; - import { TimeSeriesExplorerUrlStateManager } from './timeseriesexplorer'; +import { TimeSeriesExplorer } from '../../timeseriesexplorer'; +import { TimeSeriesExplorerPage } from '../../timeseriesexplorer/timeseriesexplorer_page'; +import { TimeseriesexplorerNoJobsFound } from '../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found'; + +jest.mock('../../services/toast_notification_service'); + +jest.mock('../../timeseriesexplorer', () => ({ + TimeSeriesExplorer: jest.fn(() => { + return null; + }), +})); + +jest.mock('../../timeseriesexplorer/timeseriesexplorer_page', () => ({ + TimeSeriesExplorerPage: jest.fn(({ children }) => { + return <>{children}; + }), +})); + +jest.mock('../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found', () => ({ + TimeseriesexplorerNoJobsFound: jest.fn(() => { + return null; + }), +})); + +const MockedTimeSeriesExplorer = TimeSeriesExplorer as jest.MockedClass; +const MockedTimeSeriesExplorerPage = TimeSeriesExplorerPage as jest.MockedFunction< + typeof TimeSeriesExplorerPage +>; +const MockedTimeseriesexplorerNoJobsFound = TimeseriesexplorerNoJobsFound as jest.MockedFunction< + typeof TimeseriesexplorerNoJobsFound +>; + +jest.mock('../../util/url_state'); + +jest.mock('../../timeseriesexplorer/hooks/use_timeseriesexplorer_url_state'); jest.mock('../../contexts/kibana/kibana_context', () => { // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -59,27 +91,22 @@ jest.mock('../../contexts/kibana/kibana_context', () => { }; }); -jest.mock('../../util/dependency_cache', () => ({ - getToastNotifications: () => ({ addSuccess: jest.fn(), addDanger: jest.fn() }), -})); - -jest.mock('../../../../shared_imports'); - describe('TimeSeriesExplorerUrlStateManager', () => { - test('Initial render shows "No single metric jobs found"', () => { + test('should render TimeseriesexplorerNoJobsFound when no jobs provided', () => { const props = { config: { get: () => 'Browser' }, jobsWithTimeRange: [], }; - const { container } = render( + render( - - - + ); - expect(container.textContent).toContain('No single metric jobs found'); + // assert + expect(MockedTimeSeriesExplorer).not.toHaveBeenCalled(); + expect(MockedTimeSeriesExplorerPage).toHaveBeenCalled(); + expect(MockedTimeseriesexplorerNoJobsFound).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index 7de59cba495af..857e894d404ae 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -11,7 +11,7 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { NavigateToPath } from '../../contexts/kibana'; +import { NavigateToPath, useNotifications } from '../../contexts/kibana'; import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; @@ -93,6 +93,7 @@ export const TimeSeriesExplorerUrlStateManager: FC { + const { toasts } = useNotifications(); const toastNotificationService = useToastNotificationService(); const [ timeSeriesExplorerUrlState, @@ -249,7 +250,12 @@ export const TimeSeriesExplorerUrlStateManager: FC { + return [{}, jest.fn()]; +}); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts index 8159dbb8ade06..26525505420de 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FC } from 'react'; +import React from 'react'; import { TimeRangeBounds } from '../explorer/explorer_utils'; -declare const TimeSeriesExplorer: FC<{ +interface Props { appStateHandler: (action: string, payload: any) => void; autoZoomDuration: number; bounds: TimeRangeBounds; @@ -21,4 +21,7 @@ declare const TimeSeriesExplorer: FC<{ tableInterval: string; tableSeverity: number; zoom?: { from?: string; to?: string }; -}>; +} + +// eslint-disable-next-line react/prefer-stateless-function +declare class TimeSeriesExplorer extends React.Component {} diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts index cd8a10a9e1f99..1781d0ee6369b 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts @@ -8,8 +8,7 @@ import { difference, without } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getToastNotifications } from '../../util/dependency_cache'; - +import { ToastsStart } from 'kibana/public'; import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; import { getTimeRangeFromSelection } from '../../components/job_selector/job_select_service_utils'; @@ -24,9 +23,9 @@ import { createTimeSeriesJobData } from './timeseriesexplorer_utils'; export function validateJobSelection( jobsWithTimeRange: MlJobWithTimeRange[], selectedJobIds: string[], - setGlobalState: (...args: any) => void + setGlobalState: (...args: any) => void, + toastNotifications: ToastsStart ) { - const toastNotifications = getToastNotifications(); const jobs = createTimeSeriesJobData(mlJobService.jobs); const timeSeriesJobIds: string[] = jobs.map((j: any) => j.id); diff --git a/x-pack/plugins/ml/public/application/util/__mocks__/url_state.tsx b/x-pack/plugins/ml/public/application/util/__mocks__/url_state.tsx new file mode 100644 index 0000000000000..cb237b951d8dd --- /dev/null +++ b/x-pack/plugins/ml/public/application/util/__mocks__/url_state.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AppStateKey } from '../url_state'; +import { TABLE_INTERVAL_DEFAULT } from '../../components/controls/select_interval/select_interval'; + +export const useUrlState = jest.fn((accessor: '_a' | '_g') => { + if (accessor === '_g') { + return [{ refreshInterval: { value: 0, pause: true } }, jest.fn()]; + } +}); + +export const usePageUrlState = jest.fn((pageKey: AppStateKey) => { + let state: unknown; + switch (pageKey) { + case 'timeseriesexplorer': + state = {}; + break; + case 'mlSelectInterval': + state = TABLE_INTERVAL_DEFAULT; + break; + } + return [state, jest.fn()]; +}); diff --git a/x-pack/plugins/ml/public/application/util/url_state.tsx b/x-pack/plugins/ml/public/application/util/url_state.tsx index 569e7bcc7b7e1..b565a0f7b7a73 100644 --- a/x-pack/plugins/ml/public/application/util/url_state.tsx +++ b/x-pack/plugins/ml/public/application/util/url_state.tsx @@ -73,7 +73,9 @@ export const urlStateStore = createContext({ searchString: '', setUrlState: () => {}, }); + const { Provider } = urlStateStore; + export const UrlStateProvider: FC = ({ children }) => { const history = useHistory(); const { search: searchString } = useLocation(); @@ -164,7 +166,7 @@ export const useUrlState = (accessor: Accessor) => { type LegacyUrlKeys = 'mlExplorerSwimlane'; -type AppStateKey = +export type AppStateKey = | 'mlSelectSeverity' | 'mlSelectInterval' | 'mlAnomaliesTable' From 0f838bfea125b92901110b2597a73c92df05c3ee Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Wed, 23 Dec 2020 09:41:49 -0500 Subject: [PATCH 40/72] [Fleet] Consistent display of agent counts on lists (#86827) * move LinkedAgentCount component to top-level components and adjust output * refactor integration details Policies list to use LinkedAgentCount component * test cases for agent counts on integrations --- .../fleet/components/linked_agent_count.tsx | 36 +++++ .../sections/agent_policy/components/index.ts | 2 +- .../components/linked_agent_count.tsx | 35 ----- .../epm/screens/detail/index.test.tsx | 115 ++++++++++++++- .../screens/detail/package_policies_panel.tsx | 31 +--- .../translations/translations/ja-JP.json | 139 +++++++++--------- .../translations/translations/zh-CN.json | 139 +++++++++--------- 7 files changed, 295 insertions(+), 202 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/linked_agent_count.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/linked_agent_count.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/linked_agent_count.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/linked_agent_count.tsx new file mode 100644 index 0000000000000..bbe7f1254a140 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/linked_agent_count.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo } from 'react'; +import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; +import { useLink } from '../hooks'; +import { AGENT_SAVED_OBJECT_TYPE } from '../constants'; + +/** + * Displays the provided `count` number as a link to the Agents list if it is greater than zero + */ +export const LinkedAgentCount = memo< + Omit & { count: number; agentPolicyId: string } +>(({ count, agentPolicyId, ...otherEuiLinkProps }) => { + const { getHref } = useLink(); + return count > 0 ? ( + + {count} + + ) : ( + + {count} + + ); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts index 1ec43f4df8c8e..ca76b65518ebe 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts @@ -8,7 +8,7 @@ export { AgentPolicyCopyProvider } from './agent_policy_copy_provider'; export { AgentPolicyDeleteProvider } from './agent_policy_delete_provider'; export { PackagePolicyDeleteProvider } from './package_policy_delete_provider'; export { AgentPolicyYamlFlyout } from './agent_policy_yaml_flyout'; -export { LinkedAgentCount } from './linked_agent_count'; +export { LinkedAgentCount } from '../../../components/linked_agent_count'; export { ConfirmDeployAgentPolicyModal } from './confirm_deploy_modal'; export { DangerEuiContextMenuItem } from './danger_eui_context_menu_item'; export { AgentPolicyActionMenu } from './actions_menu'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/linked_agent_count.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/linked_agent_count.tsx deleted file mode 100644 index c602f492f74c6..0000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/linked_agent_count.tsx +++ /dev/null @@ -1,35 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { memo } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiLink } from '@elastic/eui'; -import { useLink } from '../../../hooks'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; - -export const LinkedAgentCount = memo<{ count: number; agentPolicyId: string }>( - ({ count, agentPolicyId }) => { - const { getHref } = useLink(); - const displayValue = ( - - ); - return count > 0 ? ( - - {displayValue} - - ) : ( - displayValue - ); - } -); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx index 3d43725f2dc71..2e4c65955e0da 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx @@ -166,11 +166,27 @@ describe('when on integration detail', () => { it('should link to integration policy detail when an integration policy is clicked', async () => { await mockedApi.waitForApi(); - const firstPolicy = renderResult.getByTestId('integrationNameLink') as HTMLAnchorElement; + const firstPolicy = renderResult.getAllByTestId( + 'integrationNameLink' + )[0] as HTMLAnchorElement; expect(firstPolicy.href).toEqual( 'http://localhost/mock/app/fleet#/integrations/edit-integration/e8a37031-2907-44f6-89d2-98bd493f60dc' ); }); + + it('should NOT show link for agent count if it is zero', async () => { + await mockedApi.waitForApi(); + const firstRowAgentCount = renderResult.getAllByTestId('rowAgentCount')[0]; + expect(firstRowAgentCount.textContent).toEqual('0'); + expect(firstRowAgentCount.tagName).not.toEqual('A'); + }); + + it('should show link for agent count if greater than zero', async () => { + await mockedApi.waitForApi(); + const secondRowAgentCount = renderResult.getAllByTestId('rowAgentCount')[1]; + expect(secondRowAgentCount.textContent).toEqual('100'); + expect(secondRowAgentCount.tagName).toEqual('A'); + }); }); }); @@ -522,8 +538,87 @@ On Windows, the module was tested with Nginx installed from the Chocolatey repos updated_at: '2020-12-09T13:46:31.013Z', updated_by: 'elastic', }, + { + id: 'e3t37031-2907-44f6-89d2-5555555555', + version: 'WrrrMiwxXQ==', + name: 'nginx-2', + description: '', + namespace: 'default', + policy_id: '125c1b70-3976-11eb-ad1c-3baa423085y6', + enabled: true, + output_id: '', + inputs: [ + { + type: 'logfile', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'nginx.access' }, + vars: { paths: { value: ['/var/log/nginx/access.log*'], type: 'text' } }, + id: 'logfile-nginx.access-e8a37031-2907-44f6-89d2-98bd493f60dc', + compiled_stream: { + paths: ['/var/log/nginx/access.log*'], + exclude_files: ['.gz$'], + processors: [{ add_locale: null }], + }, + }, + { + enabled: true, + data_stream: { type: 'logs', dataset: 'nginx.error' }, + vars: { paths: { value: ['/var/log/nginx/error.log*'], type: 'text' } }, + id: 'logfile-nginx.error-e8a37031-2907-44f6-89d2-98bd493f60dc', + compiled_stream: { + paths: ['/var/log/nginx/error.log*'], + exclude_files: ['.gz$'], + multiline: { + pattern: '^\\d{4}\\/\\d{2}\\/\\d{2} ', + negate: true, + match: 'after', + }, + processors: [{ add_locale: null }], + }, + }, + { + enabled: false, + data_stream: { type: 'logs', dataset: 'nginx.ingress_controller' }, + vars: { paths: { value: ['/var/log/nginx/ingress.log*'], type: 'text' } }, + id: 'logfile-nginx.ingress_controller-e8a37031-2907-44f6-89d2-98bd493f60dc', + }, + ], + }, + { + type: 'nginx/metrics', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'nginx.stubstatus' }, + vars: { + period: { value: '10s', type: 'text' }, + server_status_path: { value: '/nginx_status', type: 'text' }, + }, + id: 'nginx/metrics-nginx.stubstatus-e8a37031-2907-44f6-89d2-98bd493f60dc', + compiled_stream: { + metricsets: ['stubstatus'], + hosts: ['http://127.0.0.1:80'], + period: '10s', + server_status_path: '/nginx_status', + }, + }, + ], + vars: { hosts: { value: ['http://127.0.0.1:80'], type: 'text' } }, + }, + ], + package: { name: 'nginx', title: 'Nginx', version: '0.3.7' }, + revision: 3, + created_at: '2020-12-09T13:46:31.013Z', + created_by: 'elastic', + updated_at: '2020-12-09T13:46:31.013Z', + updated_by: 'elastic', + }, ], - total: 1, + total: 2, page: 1, perPage: 20, }; @@ -548,8 +643,22 @@ On Windows, the module was tested with Nginx installed from the Chocolatey repos updated_by: 'elastic', agents: 0, }, + { + id: '125c1b70-3976-11eb-ad1c-3baa423085y6', + name: 'EU Healthy agents', + namespace: 'default', + description: 'Protect EU from COVID', + status: 'active', + package_policies: ['e8a37031-2907-44f6-89d2-98bd493f60cd'], + is_default: false, + monitoring_enabled: ['logs', 'metrics'], + revision: 2, + updated_at: '2020-12-09T13:46:31.840Z', + updated_by: 'elastic', + agents: 100, + }, ], - total: 1, + total: 2, page: 1, perPage: 100, }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx index 4d8cb5a16034f..c740adc4201de 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx @@ -17,10 +17,7 @@ import { FormattedRelative, FormattedMessage } from '@kbn/i18n/react'; import { useGetPackageInstallStatus } from '../../hooks'; import { InstallStatus } from '../../../../types'; import { useLink } from '../../../../hooks'; -import { - AGENT_SAVED_OBJECT_TYPE, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, -} from '../../../../../../../common/constants'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../common/constants'; import { useUrlPagination } from '../../../../hooks'; import { PackagePolicyAndAgentPolicy, @@ -28,6 +25,7 @@ import { } from './use_package_policies_with_agent_policy'; import { LinkAndRevision, LinkAndRevisionProps } from '../../../../components'; import { Persona } from './persona'; +import { LinkedAgentCount } from '../../../../components/linked_agent_count'; const IntegrationDetailsLink = memo<{ packagePolicy: PackagePolicyAndAgentPolicy['packagePolicy']; @@ -66,22 +64,6 @@ const AgentPolicyDetailLink = memo<{ ); }); -const PolicyAgentListLink = memo<{ agentPolicyId: string; children: ReactNode }>( - ({ agentPolicyId, children }) => { - const { getHref } = useLink(); - return ( - - {children} - - ); - } -); - interface PackagePoliciesPanelProps { name: string; version: string; @@ -156,9 +138,12 @@ export const PackagePoliciesPanel = ({ name, version }: PackagePoliciesPanelProp width: '8ch', render({ packagePolicy, agentPolicy }: PackagePolicyAndAgentPolicy) { return ( - - {agentPolicy?.agents ?? 0} - + ); }, }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b61532ec88c3c..fda6b81c4af03 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -248,6 +248,7 @@ "charts.colormaps.redsText": "赤", "charts.colormaps.yellowToRedText": "黄色から赤", "charts.colorPicker.setColor.screenReaderDescription": "値 {legendDataLabel} の色を設定", + "charts.countText": "カウント", "console.autocomplete.addMethodMetaText": "メソド", "console.consoleDisplayName": "コンソール", "console.consoleMenu.copyAsCurlMessage": "リクエストが URL としてコピーされました", @@ -1349,6 +1350,8 @@ "data.search.functions.kibana_context.savedSearchId.help": "クエリとフィルターに使用する保存検索ID を指定します。", "data.search.functions.kibana_context.timeRange.help": "Kibana 時間範囲フィルターを指定します", "data.search.functions.kibana.help": "Kibana グローバルコンテキストを取得します", + "data.triggers.applyFilterDescription": "Kibanaフィルターが適用されるとき。単一の値または範囲フィルターにすることができます。", + "data.triggers.applyFilterTitle": "フィルターを適用", "devTools.badge.readOnly.text": "読み込み専用", "devTools.badge.readOnly.tooltip": "を保存できませんでした", "devTools.devToolsTitle": "開発ツール", @@ -1574,6 +1577,10 @@ "embeddableApi.samples.contactCard.displayName": "連絡先カード", "embeddableApi.samples.filterableContainer.displayName": "フィルター可能なダッシュボード", "embeddableApi.samples.filterableEmbeddable.displayName": "フィルター可能", + "embeddableApi.selectRangeTrigger.description": "ビジュアライゼーションでの値の範囲", + "embeddableApi.selectRangeTrigger.title": "範囲選択", + "embeddableApi.valueClickTrigger.description": "ビジュアライゼーションでデータポイントをクリック", + "embeddableApi.valueClickTrigger.title": "シングルクリック", "esUi.cronEditor.cronDaily.fieldHour.textAtLabel": "に", "esUi.cronEditor.cronDaily.fieldTimeLabel": "時間", "esUi.cronEditor.cronDaily.hourSelectLabel": "時間", @@ -3482,12 +3489,6 @@ "uiActions.actionPanel.more": "詳細", "uiActions.actionPanel.title": "オプション", "uiActions.errors.incompatibleAction": "操作に互換性がありません", - "data.triggers.applyFilterDescription": "Kibanaフィルターが適用されるとき。単一の値または範囲フィルターにすることができます。", - "data.triggers.applyFilterTitle": "フィルターを適用", - "embeddableApi.selectRangeTrigger.description": "ビジュアライゼーションでの値の範囲", - "embeddableApi.selectRangeTrigger.title": "範囲選択", - "embeddableApi.valueClickTrigger.description": "ビジュアライゼーションでデータポイントをクリック", - "embeddableApi.valueClickTrigger.title": "シングルクリック", "usageCollection.stats.notReadyMessage": "まだ統計が準備できていません。しばらくたってから再試行してください。", "visDefaultEditor.advancedToggle.advancedLinkLabel": "高度な設定", "visDefaultEditor.agg.toggleEditorButtonAriaLabel": "{schema} エディターを切り替える", @@ -4305,27 +4306,6 @@ "visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsText": "1つのデータソースが返せるバケットの最大数です。値が大きいとブラウザのレンダリング速度が下がる可能性があります。", "visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsTitle": "ヒートマップの最大バケット数", "visTypeVislib.aggResponse.allDocsTitle": "すべてのドキュメント", - "visTypeXy.area.areaTitle": "エリア", - "charts.countText": "カウント", - "visTypeXy.area.groupTitle": "系列を分割", - "visTypeXy.area.metricsTitle": "Y 軸", - "visTypeXy.area.radiusTitle": "点のサイズ", - "visTypeXy.area.segmentTitle": "X 軸", - "visTypeXy.area.splitTitle": "チャートを分割", - "visTypeXy.area.tabs.metricsAxesTitle": "メトリックと軸", - "visTypeXy.area.tabs.panelSettingsTitle": "パネル設定", - "visTypeXy.axisModes.normalText": "標準", - "visTypeXy.axisModes.percentageText": "割合 (%)", - "visTypeXy.axisModes.silhouetteText": "シルエット", - "visTypeXy.axisModes.wiggleText": "振動", - "visTypeXy.categoryAxis.rotate.angledText": "傾斜", - "visTypeXy.categoryAxis.rotate.horizontalText": "横", - "visTypeXy.categoryAxis.rotate.verticalText": "縦", - "visTypeXy.chartModes.normalText": "標準", - "visTypeXy.chartModes.stackedText": "スタック", - "visTypeXy.chartTypes.areaText": "エリア", - "visTypeXy.chartTypes.barText": "バー", - "visTypeXy.chartTypes.lineText": "折れ線", "visTypeVislib.controls.gaugeOptions.alignmentLabel": "アラインメント", "visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "範囲を自動拡張", "visTypeVislib.controls.gaugeOptions.displayWarningsLabel": "警告を表示", @@ -4351,6 +4331,68 @@ "visTypeVislib.controls.heatmapOptions.scaleToDataBoundsLabel": "データバウンドに合わせる", "visTypeVislib.controls.heatmapOptions.showLabelsTitle": "ラベルを表示", "visTypeVislib.controls.heatmapOptions.useCustomRangesLabel": "カスタム範囲を使用", + "visTypeVislib.editors.heatmap.basicSettingsTitle": "基本設定", + "visTypeVislib.editors.heatmap.heatmapSettingsTitle": "ヒートマップ設定", + "visTypeVislib.editors.heatmap.highlightLabel": "ハイライト範囲", + "visTypeVislib.editors.heatmap.highlightLabelTooltip": "チャートのカーソルを当てた部分と凡例の対応するラベルをハイライトします。", + "visTypeVislib.editors.pie.donutLabel": "ドーナッツ", + "visTypeVislib.editors.pie.labelsSettingsTitle": "ラベル設定", + "visTypeVislib.editors.pie.pieSettingsTitle": "パイ設定", + "visTypeVislib.editors.pie.showLabelsLabel": "ラベルを表示", + "visTypeVislib.editors.pie.showTopLevelOnlyLabel": "トップレベルのみ表示", + "visTypeVislib.editors.pie.showValuesLabel": "値を表示", + "visTypeVislib.functions.pie.help": "パイビジュアライゼーション", + "visTypeVislib.functions.vislib.help": "Vislib ビジュアライゼーション", + "visTypeVislib.gauge.alignmentAutomaticTitle": "自動", + "visTypeVislib.gauge.alignmentHorizontalTitle": "横", + "visTypeVislib.gauge.alignmentVerticalTitle": "縦", + "visTypeVislib.gauge.gaugeTitle": "ゲージ", + "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", + "visTypeVislib.gauge.gaugeTypes.circleText": "円", + "visTypeVislib.gauge.groupTitle": "グループを分割", + "visTypeVislib.gauge.metricTitle": "メトリック", + "visTypeVislib.goal.goalTitle": "ゴール", + "visTypeVislib.goal.groupTitle": "グループを分割", + "visTypeVislib.goal.metricTitle": "メトリック", + "visTypeVislib.heatmap.groupTitle": "Y 軸", + "visTypeVislib.heatmap.metricTitle": "値", + "visTypeVislib.heatmap.segmentTitle": "X 軸", + "visTypeVislib.heatmap.splitTitle": "チャートを分割", + "visTypeVislib.pie.metricTitle": "サイズのスライス", + "visTypeVislib.pie.pieTitle": "パイ", + "visTypeVislib.pie.segmentTitle": "スライスの分割", + "visTypeVislib.pie.splitTitle": "チャートを分割", + "visTypeVislib.vislib.errors.noResultsFoundTitle": "結果が見つかりませんでした", + "visTypeVislib.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます ({nr})。構成されている最大値は {max} です。", + "visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "値 {legendDataLabel} でフィルタリング", + "visTypeVislib.vislib.legend.filterOptionsLegend": "{legendDataLabel}、フィルターオプション", + "visTypeVislib.vislib.legend.filterOutValueButtonAriaLabel": "値 {legendDataLabel} を除外", + "visTypeVislib.vislib.legend.loadingLabel": "読み込み中…", + "visTypeVislib.vislib.legend.toggleLegendButtonAriaLabel": "凡例を切り替える", + "visTypeVislib.vislib.legend.toggleLegendButtonTitle": "凡例を切り替える", + "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}、トグルオプション", + "visTypeVislib.vislib.tooltip.fieldLabel": "フィールド", + "visTypeVislib.vislib.tooltip.valueLabel": "値", + "visTypeXy.area.areaTitle": "エリア", + "visTypeXy.area.groupTitle": "系列を分割", + "visTypeXy.area.metricsTitle": "Y 軸", + "visTypeXy.area.radiusTitle": "点のサイズ", + "visTypeXy.area.segmentTitle": "X 軸", + "visTypeXy.area.splitTitle": "チャートを分割", + "visTypeXy.area.tabs.metricsAxesTitle": "メトリックと軸", + "visTypeXy.area.tabs.panelSettingsTitle": "パネル設定", + "visTypeXy.axisModes.normalText": "標準", + "visTypeXy.axisModes.percentageText": "割合 (%)", + "visTypeXy.axisModes.silhouetteText": "シルエット", + "visTypeXy.axisModes.wiggleText": "振動", + "visTypeXy.categoryAxis.rotate.angledText": "傾斜", + "visTypeXy.categoryAxis.rotate.horizontalText": "横", + "visTypeXy.categoryAxis.rotate.verticalText": "縦", + "visTypeXy.chartModes.normalText": "標準", + "visTypeXy.chartModes.stackedText": "スタック", + "visTypeXy.chartTypes.areaText": "エリア", + "visTypeXy.chartTypes.barText": "バー", + "visTypeXy.chartTypes.lineText": "折れ線", "visTypeXy.controls.pointSeries.categoryAxis.alignLabel": "配置", "visTypeXy.controls.pointSeries.categoryAxis.filterLabelsLabel": "フィルターラベル", "visTypeXy.controls.pointSeries.categoryAxis.labelsTitle": "ラベル", @@ -4393,16 +4435,6 @@ "visTypeXy.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "{axisName} オプションを切り替える", "visTypeXy.controls.pointSeries.valueAxes.yAxisTitle": "Y 軸", "visTypeXy.controls.truncateLabel": "切り捨て", - "visTypeVislib.editors.heatmap.basicSettingsTitle": "基本設定", - "visTypeVislib.editors.heatmap.heatmapSettingsTitle": "ヒートマップ設定", - "visTypeVislib.editors.heatmap.highlightLabel": "ハイライト範囲", - "visTypeVislib.editors.heatmap.highlightLabelTooltip": "チャートのカーソルを当てた部分と凡例の対応するラベルをハイライトします。", - "visTypeVislib.editors.pie.donutLabel": "ドーナッツ", - "visTypeVislib.editors.pie.labelsSettingsTitle": "ラベル設定", - "visTypeVislib.editors.pie.pieSettingsTitle": "パイ設定", - "visTypeVislib.editors.pie.showLabelsLabel": "ラベルを表示", - "visTypeVislib.editors.pie.showTopLevelOnlyLabel": "トップレベルのみ表示", - "visTypeVislib.editors.pie.showValuesLabel": "値を表示", "visTypeXy.editors.pointSeries.currentTimeMarkerLabel": "現在時刻マーカー", "visTypeXy.editors.pointSeries.orderBucketsBySumLabel": "バケットを合計で並べ替え", "visTypeXy.editors.pointSeries.settingsTitle": "設定", @@ -4413,23 +4445,6 @@ "visTypeXy.editors.pointSeries.thresholdLine.valueLabel": "しきい値", "visTypeXy.editors.pointSeries.thresholdLine.widthLabel": "線の幅", "visTypeXy.editors.pointSeries.thresholdLineSettingsTitle": "しきい線", - "visTypeVislib.functions.pie.help": "パイビジュアライゼーション", - "visTypeVislib.functions.vislib.help": "Vislib ビジュアライゼーション", - "visTypeVislib.gauge.alignmentAutomaticTitle": "自動", - "visTypeVislib.gauge.alignmentHorizontalTitle": "横", - "visTypeVislib.gauge.alignmentVerticalTitle": "縦", - "visTypeVislib.gauge.gaugeTitle": "ゲージ", - "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", - "visTypeVislib.gauge.gaugeTypes.circleText": "円", - "visTypeVislib.gauge.groupTitle": "グループを分割", - "visTypeVislib.gauge.metricTitle": "メトリック", - "visTypeVislib.goal.goalTitle": "ゴール", - "visTypeVislib.goal.groupTitle": "グループを分割", - "visTypeVislib.goal.metricTitle": "メトリック", - "visTypeVislib.heatmap.groupTitle": "Y 軸", - "visTypeVislib.heatmap.metricTitle": "値", - "visTypeVislib.heatmap.segmentTitle": "X 軸", - "visTypeVislib.heatmap.splitTitle": "チャートを分割", "visTypeXy.histogram.groupTitle": "系列を分割", "visTypeXy.histogram.metricTitle": "Y 軸", "visTypeXy.histogram.radiusTitle": "点のサイズ", @@ -4453,27 +4468,12 @@ "visTypeXy.line.radiusTitle": "点のサイズ", "visTypeXy.line.segmentTitle": "X 軸", "visTypeXy.line.splitTitle": "チャートを分割", - "visTypeVislib.pie.metricTitle": "サイズのスライス", - "visTypeVislib.pie.pieTitle": "パイ", - "visTypeVislib.pie.segmentTitle": "スライスの分割", - "visTypeVislib.pie.splitTitle": "チャートを分割", "visTypeXy.scaleTypes.linearText": "線形", "visTypeXy.scaleTypes.logText": "ログ", "visTypeXy.scaleTypes.squareRootText": "平方根", "visTypeXy.thresholdLine.style.dashedText": "鎖線", "visTypeXy.thresholdLine.style.dotdashedText": "点線", "visTypeXy.thresholdLine.style.fullText": "完全", - "visTypeVislib.vislib.errors.noResultsFoundTitle": "結果が見つかりませんでした", - "visTypeVislib.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます ({nr})。構成されている最大値は {max} です。", - "visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "値 {legendDataLabel} でフィルタリング", - "visTypeVislib.vislib.legend.filterOptionsLegend": "{legendDataLabel}、フィルターオプション", - "visTypeVislib.vislib.legend.filterOutValueButtonAriaLabel": "値 {legendDataLabel} を除外", - "visTypeVislib.vislib.legend.loadingLabel": "読み込み中…", - "visTypeVislib.vislib.legend.toggleLegendButtonAriaLabel": "凡例を切り替える", - "visTypeVislib.vislib.legend.toggleLegendButtonTitle": "凡例を切り替える", - "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}、トグルオプション", - "visTypeVislib.vislib.tooltip.fieldLabel": "フィールド", - "visTypeVislib.vislib.tooltip.valueLabel": "値", "visualizations.advancedSettings.visualizeEnableLabsText": "ユーザーが実験的なビジュアライゼーションを作成、表示、編集できるようになります。無効の場合、\n ユーザーは本番準備が整ったビジュアライゼーションのみを利用できます。", "visualizations.advancedSettings.visualizeEnableLabsTitle": "実験的なビジュアライゼーションを有効にする", "visualizations.disabledLabVisualizationMessage": "ラボビジュアライゼーションを表示するには、高度な設定でラボモードをオンにしてください。", @@ -7153,7 +7153,6 @@ "xpack.fleet.agentPolicy.confirmModalConfirmButtonLabel": "変更を保存してデプロイ", "xpack.fleet.agentPolicy.confirmModalDescription": "このアクションは元に戻せません。続行していいですか?", "xpack.fleet.agentPolicy.confirmModalTitle": "変更を保存してデプロイ", - "xpack.fleet.agentPolicy.linkedAgentCountText": "{count, plural, one {#件のエージェント} other {#件のエージェント}}", "xpack.fleet.agentPolicyActionMenu.buttonText": "アクション", "xpack.fleet.agentPolicyActionMenu.copyPolicyActionText": "ポリシーをコピー", "xpack.fleet.agentPolicyActionMenu.enrollAgentActionText": "エージェントの追加", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5377ae790c601..609e09d0197af 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -248,6 +248,7 @@ "charts.colormaps.redsText": "红色", "charts.colormaps.yellowToRedText": "黄到红", "charts.colorPicker.setColor.screenReaderDescription": "为值 {legendDataLabel} 设置颜色", + "charts.countText": "计数", "console.autocomplete.addMethodMetaText": "方法", "console.consoleDisplayName": "控制台", "console.consoleMenu.copyAsCurlMessage": "请求已复制为 cURL", @@ -1350,6 +1351,8 @@ "data.search.functions.kibana_context.savedSearchId.help": "指定要用于查询和筛选的已保存搜索 ID", "data.search.functions.kibana_context.timeRange.help": "指定 Kibana 时间范围筛选", "data.search.functions.kibana.help": "获取 kibana 全局上下文", + "data.triggers.applyFilterDescription": "应用 kibana 筛选时。可能是单个值或范围筛选。", + "data.triggers.applyFilterTitle": "应用筛选", "devTools.badge.readOnly.text": "只读", "devTools.badge.readOnly.tooltip": "无法保存", "devTools.devToolsTitle": "开发工具", @@ -1575,6 +1578,10 @@ "embeddableApi.samples.contactCard.displayName": "联系卡片", "embeddableApi.samples.filterableContainer.displayName": "可筛选仪表板", "embeddableApi.samples.filterableEmbeddable.displayName": "可筛选", + "embeddableApi.selectRangeTrigger.description": "可视化上的一组值", + "embeddableApi.selectRangeTrigger.title": "范围选择", + "embeddableApi.valueClickTrigger.description": "可视化上的数据点单击", + "embeddableApi.valueClickTrigger.title": "单击", "esUi.cronEditor.cronDaily.fieldHour.textAtLabel": "在", "esUi.cronEditor.cronDaily.fieldTimeLabel": "时间", "esUi.cronEditor.cronDaily.hourSelectLabel": "小时", @@ -3483,12 +3490,6 @@ "uiActions.actionPanel.more": "更多", "uiActions.actionPanel.title": "选项", "uiActions.errors.incompatibleAction": "操作不兼容", - "data.triggers.applyFilterDescription": "应用 kibana 筛选时。可能是单个值或范围筛选。", - "data.triggers.applyFilterTitle": "应用筛选", - "embeddableApi.selectRangeTrigger.description": "可视化上的一组值", - "embeddableApi.selectRangeTrigger.title": "范围选择", - "embeddableApi.valueClickTrigger.description": "可视化上的数据点单击", - "embeddableApi.valueClickTrigger.title": "单击", "usageCollection.stats.notReadyMessage": "统计信息尚未准备就绪。请稍后重试。", "visDefaultEditor.advancedToggle.advancedLinkLabel": "高级", "visDefaultEditor.agg.toggleEditorButtonAriaLabel": "切换 {schema} 编辑器", @@ -4307,27 +4308,6 @@ "visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsText": "单个数据源可以返回的最大存储桶数目。较高的数目可能对浏览器呈现性能有负面影响", "visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsTitle": "热图最大存储桶数", "visTypeVislib.aggResponse.allDocsTitle": "所有文档", - "visTypeXy.area.areaTitle": "面积图", - "charts.countText": "计数", - "visTypeXy.area.groupTitle": "拆分序列", - "visTypeXy.area.metricsTitle": "Y 轴", - "visTypeXy.area.radiusTitle": "点大小", - "visTypeXy.area.segmentTitle": "X 轴", - "visTypeXy.area.splitTitle": "拆分图表", - "visTypeXy.area.tabs.metricsAxesTitle": "指标和轴", - "visTypeXy.area.tabs.panelSettingsTitle": "面板设置", - "visTypeXy.axisModes.normalText": "正常", - "visTypeXy.axisModes.percentageText": "百分比", - "visTypeXy.axisModes.silhouetteText": "剪影", - "visTypeXy.axisModes.wiggleText": "扭动", - "visTypeXy.categoryAxis.rotate.angledText": "带角度", - "visTypeXy.categoryAxis.rotate.horizontalText": "水平", - "visTypeXy.categoryAxis.rotate.verticalText": "垂直", - "visTypeXy.chartModes.normalText": "正常", - "visTypeXy.chartModes.stackedText": "堆叠", - "visTypeXy.chartTypes.areaText": "面积图", - "visTypeXy.chartTypes.barText": "条形图", - "visTypeXy.chartTypes.lineText": "折线图", "visTypeVislib.controls.gaugeOptions.alignmentLabel": "对齐方式", "visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "自动扩展范围", "visTypeVislib.controls.gaugeOptions.displayWarningsLabel": "显示警告", @@ -4353,6 +4333,68 @@ "visTypeVislib.controls.heatmapOptions.scaleToDataBoundsLabel": "缩放到数据边界", "visTypeVislib.controls.heatmapOptions.showLabelsTitle": "显示标签", "visTypeVislib.controls.heatmapOptions.useCustomRangesLabel": "使用定制范围", + "visTypeVislib.editors.heatmap.basicSettingsTitle": "基本设置", + "visTypeVislib.editors.heatmap.heatmapSettingsTitle": "热图设置", + "visTypeVislib.editors.heatmap.highlightLabel": "高亮范围", + "visTypeVislib.editors.heatmap.highlightLabelTooltip": "高亮显示图表中鼠标悬停的范围以及图例中对应的标签。", + "visTypeVislib.editors.pie.donutLabel": "圆环图", + "visTypeVislib.editors.pie.labelsSettingsTitle": "标签设置", + "visTypeVislib.editors.pie.pieSettingsTitle": "饼图设置", + "visTypeVislib.editors.pie.showLabelsLabel": "显示标签", + "visTypeVislib.editors.pie.showTopLevelOnlyLabel": "仅显示顶级", + "visTypeVislib.editors.pie.showValuesLabel": "显示值", + "visTypeVislib.functions.pie.help": "饼图可视化", + "visTypeVislib.functions.vislib.help": "Vislib 可视化", + "visTypeVislib.gauge.alignmentAutomaticTitle": "自动", + "visTypeVislib.gauge.alignmentHorizontalTitle": "水平", + "visTypeVislib.gauge.alignmentVerticalTitle": "垂直", + "visTypeVislib.gauge.gaugeTitle": "仪表盘图", + "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", + "visTypeVislib.gauge.gaugeTypes.circleText": "圆形", + "visTypeVislib.gauge.groupTitle": "拆分组", + "visTypeVislib.gauge.metricTitle": "指标", + "visTypeVislib.goal.goalTitle": "目标图", + "visTypeVislib.goal.groupTitle": "拆分组", + "visTypeVislib.goal.metricTitle": "指标", + "visTypeVislib.heatmap.groupTitle": "Y 轴", + "visTypeVislib.heatmap.metricTitle": "值", + "visTypeVislib.heatmap.segmentTitle": "X 轴", + "visTypeVislib.heatmap.splitTitle": "拆分图表", + "visTypeVislib.pie.metricTitle": "切片大小", + "visTypeVislib.pie.pieTitle": "饼图", + "visTypeVislib.pie.segmentTitle": "拆分切片", + "visTypeVislib.pie.splitTitle": "拆分图表", + "visTypeVislib.vislib.errors.noResultsFoundTitle": "找不到结果", + "visTypeVislib.vislib.heatmap.maxBucketsText": "定义了过多的序列 ({nr})。配置的最大值为 {max}。", + "visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "筛留值 {legendDataLabel}", + "visTypeVislib.vislib.legend.filterOptionsLegend": "{legendDataLabel}, 筛选选项", + "visTypeVislib.vislib.legend.filterOutValueButtonAriaLabel": "筛除值 {legendDataLabel}", + "visTypeVislib.vislib.legend.loadingLabel": "正在加载……", + "visTypeVislib.vislib.legend.toggleLegendButtonAriaLabel": "切换图例", + "visTypeVislib.vislib.legend.toggleLegendButtonTitle": "切换图例", + "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}, 切换选项", + "visTypeVislib.vislib.tooltip.fieldLabel": "字段", + "visTypeVislib.vislib.tooltip.valueLabel": "值", + "visTypeXy.area.areaTitle": "面积图", + "visTypeXy.area.groupTitle": "拆分序列", + "visTypeXy.area.metricsTitle": "Y 轴", + "visTypeXy.area.radiusTitle": "点大小", + "visTypeXy.area.segmentTitle": "X 轴", + "visTypeXy.area.splitTitle": "拆分图表", + "visTypeXy.area.tabs.metricsAxesTitle": "指标和轴", + "visTypeXy.area.tabs.panelSettingsTitle": "面板设置", + "visTypeXy.axisModes.normalText": "正常", + "visTypeXy.axisModes.percentageText": "百分比", + "visTypeXy.axisModes.silhouetteText": "剪影", + "visTypeXy.axisModes.wiggleText": "扭动", + "visTypeXy.categoryAxis.rotate.angledText": "带角度", + "visTypeXy.categoryAxis.rotate.horizontalText": "水平", + "visTypeXy.categoryAxis.rotate.verticalText": "垂直", + "visTypeXy.chartModes.normalText": "正常", + "visTypeXy.chartModes.stackedText": "堆叠", + "visTypeXy.chartTypes.areaText": "面积图", + "visTypeXy.chartTypes.barText": "条形图", + "visTypeXy.chartTypes.lineText": "折线图", "visTypeXy.controls.pointSeries.categoryAxis.alignLabel": "对齐", "visTypeXy.controls.pointSeries.categoryAxis.filterLabelsLabel": "筛选标签", "visTypeXy.controls.pointSeries.categoryAxis.labelsTitle": "标签", @@ -4395,16 +4437,6 @@ "visTypeXy.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "切换 {axisName} 选项", "visTypeXy.controls.pointSeries.valueAxes.yAxisTitle": "Y 轴", "visTypeXy.controls.truncateLabel": "截断", - "visTypeVislib.editors.heatmap.basicSettingsTitle": "基本设置", - "visTypeVislib.editors.heatmap.heatmapSettingsTitle": "热图设置", - "visTypeVislib.editors.heatmap.highlightLabel": "高亮范围", - "visTypeVislib.editors.heatmap.highlightLabelTooltip": "高亮显示图表中鼠标悬停的范围以及图例中对应的标签。", - "visTypeVislib.editors.pie.donutLabel": "圆环图", - "visTypeVislib.editors.pie.labelsSettingsTitle": "标签设置", - "visTypeVislib.editors.pie.pieSettingsTitle": "饼图设置", - "visTypeVislib.editors.pie.showLabelsLabel": "显示标签", - "visTypeVislib.editors.pie.showTopLevelOnlyLabel": "仅显示顶级", - "visTypeVislib.editors.pie.showValuesLabel": "显示值", "visTypeXy.editors.pointSeries.currentTimeMarkerLabel": "当前时间标记", "visTypeXy.editors.pointSeries.orderBucketsBySumLabel": "按总计值排序存储桶", "visTypeXy.editors.pointSeries.settingsTitle": "设置", @@ -4415,23 +4447,6 @@ "visTypeXy.editors.pointSeries.thresholdLine.valueLabel": "阈值", "visTypeXy.editors.pointSeries.thresholdLine.widthLabel": "线条宽度", "visTypeXy.editors.pointSeries.thresholdLineSettingsTitle": "阈值线条", - "visTypeVislib.functions.pie.help": "饼图可视化", - "visTypeVislib.functions.vislib.help": "Vislib 可视化", - "visTypeVislib.gauge.alignmentAutomaticTitle": "自动", - "visTypeVislib.gauge.alignmentHorizontalTitle": "水平", - "visTypeVislib.gauge.alignmentVerticalTitle": "垂直", - "visTypeVislib.gauge.gaugeTitle": "仪表盘图", - "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", - "visTypeVislib.gauge.gaugeTypes.circleText": "圆形", - "visTypeVislib.gauge.groupTitle": "拆分组", - "visTypeVislib.gauge.metricTitle": "指标", - "visTypeVislib.goal.goalTitle": "目标图", - "visTypeVislib.goal.groupTitle": "拆分组", - "visTypeVislib.goal.metricTitle": "指标", - "visTypeVislib.heatmap.groupTitle": "Y 轴", - "visTypeVislib.heatmap.metricTitle": "值", - "visTypeVislib.heatmap.segmentTitle": "X 轴", - "visTypeVislib.heatmap.splitTitle": "拆分图表", "visTypeXy.histogram.groupTitle": "拆分序列", "visTypeXy.histogram.metricTitle": "Y 轴", "visTypeXy.histogram.radiusTitle": "点大小", @@ -4455,27 +4470,12 @@ "visTypeXy.line.radiusTitle": "点大小", "visTypeXy.line.segmentTitle": "X 轴", "visTypeXy.line.splitTitle": "拆分图表", - "visTypeVislib.pie.metricTitle": "切片大小", - "visTypeVislib.pie.pieTitle": "饼图", - "visTypeVislib.pie.segmentTitle": "拆分切片", - "visTypeVislib.pie.splitTitle": "拆分图表", "visTypeXy.scaleTypes.linearText": "线性", "visTypeXy.scaleTypes.logText": "对数", "visTypeXy.scaleTypes.squareRootText": "平方根", "visTypeXy.thresholdLine.style.dashedText": "虚线", "visTypeXy.thresholdLine.style.dotdashedText": "点虚线", "visTypeXy.thresholdLine.style.fullText": "实线", - "visTypeVislib.vislib.errors.noResultsFoundTitle": "找不到结果", - "visTypeVislib.vislib.heatmap.maxBucketsText": "定义了过多的序列 ({nr})。配置的最大值为 {max}。", - "visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "筛留值 {legendDataLabel}", - "visTypeVislib.vislib.legend.filterOptionsLegend": "{legendDataLabel}, 筛选选项", - "visTypeVislib.vislib.legend.filterOutValueButtonAriaLabel": "筛除值 {legendDataLabel}", - "visTypeVislib.vislib.legend.loadingLabel": "正在加载……", - "visTypeVislib.vislib.legend.toggleLegendButtonAriaLabel": "切换图例", - "visTypeVislib.vislib.legend.toggleLegendButtonTitle": "切换图例", - "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}, 切换选项", - "visTypeVislib.vislib.tooltip.fieldLabel": "字段", - "visTypeVislib.vislib.tooltip.valueLabel": "值", "visualizations.advancedSettings.visualizeEnableLabsText": "允许用户创建、查看和编辑实验性可视化。如果禁用,\n 仅被视为生产就绪的可视化可供用户使用。", "visualizations.advancedSettings.visualizeEnableLabsTitle": "启用实验性可视化", "visualizations.disabledLabVisualizationMessage": "请在高级设置中打开实验室模式,以查看实验室可视化。", @@ -7160,7 +7160,6 @@ "xpack.fleet.agentPolicy.confirmModalConfirmButtonLabel": "保存并部署更改", "xpack.fleet.agentPolicy.confirmModalDescription": "此操作无法撤消。是否确定要继续?", "xpack.fleet.agentPolicy.confirmModalTitle": "保存并部署更改", - "xpack.fleet.agentPolicy.linkedAgentCountText": "{count, plural, one {# 个代理} other {# 个代理}}", "xpack.fleet.agentPolicyActionMenu.buttonText": "操作", "xpack.fleet.agentPolicyActionMenu.copyPolicyActionText": "复制策略", "xpack.fleet.agentPolicyActionMenu.enrollAgentActionText": "添加代理", From e575af338669eaa9c4e2286e9b675b20ea722723 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Wed, 23 Dec 2020 15:53:44 +0100 Subject: [PATCH 41/72] [Expression Renderer] Fix Expression Renderer className composition (#86094) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/react_expression_renderer.test.tsx | 18 ++++++++++++++++++ .../public/react_expression_renderer.tsx | 3 +-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index 4ebd626e70fc3..ac6fcab33acbf 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -304,4 +304,22 @@ describe('ExpressionRenderer', () => { expect(onEvent).toHaveBeenCalledTimes(1); expect(onEvent.mock.calls[0][0]).toBe(event); }); + + it('should correctly assign classes to the wrapper node', () => { + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$: new Subject(), + data$: new Subject(), + loading$: new Subject(), + update: jest.fn(), + destroy: jest.fn(), + }; + }); + + const instance = mount(); + // Counte is 2 because the class is applied to ReactExpressionRenderer + internal component + expect(instance.find('.myClassName').length).toBe(2); + + instance.unmount(); + }); }); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index d19f482107845..3227b34dcc1ff 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -211,10 +211,9 @@ export const ReactExpressionRenderer = ({ } }, [state.error]); - const classes = classNames('expExpressionRenderer', { + const classes = classNames('expExpressionRenderer', className, { 'expExpressionRenderer-isEmpty': state.isEmpty, 'expExpressionRenderer-hasError': !!state.error, - className, }); const expressionStyles: React.CSSProperties = {}; From ca685f01fcd081c4aaa27f0da79bbccee3ec89a2 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Wed, 23 Dec 2020 18:22:56 +0300 Subject: [PATCH 42/72] Removed unneeded dependency from hook (#86888) --- src/plugins/vis_type_vislib/public/vis_wrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/vis_type_vislib/public/vis_wrapper.tsx b/src/plugins/vis_type_vislib/public/vis_wrapper.tsx index b8dbd0f945c32..e2e8a98a9a8b6 100644 --- a/src/plugins/vis_type_vislib/public/vis_wrapper.tsx +++ b/src/plugins/vis_type_vislib/public/vis_wrapper.tsx @@ -61,7 +61,7 @@ const VislibWrapper = ({ core, charts, visData, visConfig, handlers }: VislibWra visController.current?.destroy(); visController.current = null; }; - }, [core, charts, handlers]); + }, [core, charts]); useEffect(updateChart, [updateChart]); From 2cc2312f6d7a98f91ae8229c7a6509d3110e74a9 Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 23 Dec 2020 10:39:21 -0800 Subject: [PATCH 43/72] [App Search] Result component - a11y enhancements (#86841) * Refactor Result card layout - Move toggle action to the bottom of the card content - [TODO] Action button to the right will be used for new link button (separate for accessibility/screen readers) - Use grid to get the layout we want without extra div wrappers * Add action button link to document detail + remove tag on article content - should have onClick only - this allows screenreaders to granularly navigate through the card content while allowing mouse users the entire card to click - the new actionButton details link is accessible to both keyboard & screen reader users * [Polish] Hover effects to help guide mouse users * [i18n] Add pluralization to fields copy * Update tests * [Cleanup] Remove unneeded wrapper * [??] More specific title for result group - since the aria-label for the new detail button link is basically that --- .../app_search/components/result/result.scss | 57 +++++++++-- .../components/result/result.test.tsx | 69 +++++++------ .../app_search/components/result/result.tsx | 97 +++++++++++-------- 3 files changed, 142 insertions(+), 81 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss index 8342061ee00c3..f69acbdaba150 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss @@ -1,17 +1,43 @@ .appSearchResult { - display: flex; + display: grid; + grid-template-columns: 1fr auto; + grid-template-rows: 1fr auto; + grid-template-areas: + 'content actions' + 'toggle actions'; + overflow: hidden; // Prevents child background-colors from clipping outside of panel border-radius &__content { + grid-area: content; width: 100%; padding: $euiSize; overflow: hidden; color: $euiTextColor; } - &__hiddenFieldsIndicator { + &__hiddenFieldsToggle { + grid-area: toggle; + display: flex; + justify-content: center; + padding: $euiSizeS; + border-top: $euiBorderThin; font-size: $euiFontSizeXS; - color: $euiColorDarkShade; - margin-top: $euiSizeS; + color: $euiColorPrimary; + + &:hover, + &:focus { + background-color: $euiPageBackgroundColor; + } + + .euiIcon { + margin-left: $euiSizeXS; + } + } + + &__actionButtons { + grid-area: actions; + display: flex; + flex-wrap: no-wrap; } &__actionButton { @@ -22,10 +48,27 @@ border-left: $euiBorderThin; &:hover, - &:focus, - &:active { + &:focus { background-color: $euiPageBackgroundColor; - cursor: pointer; } } } + +/** + * CSS for hover specific logic + * It's mildly horrific, so I pulled it out to its own section here + */ + +.appSearchResult--link { + &:hover, + &:focus { + @include euiSlightShadowHover; + } +} +.appSearchResult__content--link:hover { + cursor: pointer; + + & ~ .appSearchResult__actionButtons .appSearchResult__actionButton--link { + background-color: $euiPageBackgroundColor; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx index c4de24e78eae5..973fc6226910a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx @@ -49,6 +49,7 @@ describe('Result', () => { it('renders', () => { const wrapper = shallow(); expect(wrapper.find(EuiPanel).exists()).toBe(true); + expect(wrapper.find(EuiPanel).prop('title')).toEqual('Document 1'); }); it('should render a ResultField for each field except id and _meta', () => { @@ -76,16 +77,20 @@ describe('Result', () => { describe('document detail link', () => { it('will render a link if shouldLinkToDetailPage is true', () => { const wrapper = shallow(); - expect(wrapper.find(ReactRouterHelper).prop('to')).toEqual('/engines/my-engine/documents/1'); - expect(wrapper.find('article.appSearchResult__content').exists()).toBe(false); - expect(wrapper.find('a.appSearchResult__content').exists()).toBe(true); + wrapper.find(ReactRouterHelper).forEach((link) => { + expect(link.prop('to')).toEqual('/engines/my-engine/documents/1'); + }); + expect(wrapper.hasClass('appSearchResult--link')).toBe(true); + expect(wrapper.find('.appSearchResult__content--link').exists()).toBe(true); + expect(wrapper.find('.appSearchResult__actionButton--link').exists()).toBe(true); }); it('will not render a link if shouldLinkToDetailPage is not set', () => { const wrapper = shallow(); expect(wrapper.find(ReactRouterHelper).exists()).toBe(false); - expect(wrapper.find('article.appSearchResult__content').exists()).toBe(true); - expect(wrapper.find('a.appSearchResult__content').exists()).toBe(false); + expect(wrapper.hasClass('appSearchResult--link')).toBe(false); + expect(wrapper.find('.appSearchResult__content--link').exists()).toBe(false); + expect(wrapper.find('.appSearchResult__actionButton--link').exists()).toBe(false); }); }); @@ -140,18 +145,16 @@ describe('Result', () => { wrapper = shallow(); }); - it('renders a collapse button', () => { - expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(false); + it('renders a hidden fields toggle button', () => { + expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').exists()).toBe(true); }); - it('does not render an expand button', () => { - expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(true); + it('renders a collapse icon', () => { + expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(false); }); - it('renders a hidden fields indicator', () => { - expect(wrapper.find('.appSearchResult__hiddenFieldsIndicator').text()).toEqual( - '1 more fields' - ); + it('does not render an expand icon', () => { + expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(true); }); it('shows no more than 5 fields', () => { @@ -164,20 +167,22 @@ describe('Result', () => { beforeAll(() => { wrapper = shallow(); - expect(wrapper.find('.appSearchResult__actionButton').exists()).toBe(true); - wrapper.find('.appSearchResult__actionButton').simulate('click'); + expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').exists()).toBe(true); + wrapper.find('.appSearchResult__hiddenFieldsToggle').simulate('click'); }); - it('renders a collapse button', () => { - expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(true); + it('renders correct toggle text', () => { + expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').text()).toEqual( + 'Hide additional fields' + ); }); - it('does not render an expand button', () => { - expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(false); + it('renders a collapse icon', () => { + expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(true); }); - it('does not render a hidden fields indicator', () => { - expect(wrapper.find('.appSearchResult__hiddenFieldsIndicator').exists()).toBe(false); + it('does not render an expand icon', () => { + expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(false); }); it('shows all fields', () => { @@ -190,23 +195,23 @@ describe('Result', () => { beforeAll(() => { wrapper = shallow(); - expect(wrapper.find('.appSearchResult__actionButton').exists()).toBe(true); - wrapper.find('.appSearchResult__actionButton').simulate('click'); - wrapper.find('.appSearchResult__actionButton').simulate('click'); + expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').exists()).toBe(true); + wrapper.find('.appSearchResult__hiddenFieldsToggle').simulate('click'); + wrapper.find('.appSearchResult__hiddenFieldsToggle').simulate('click'); }); - it('renders a collapse button', () => { - expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(false); + it('renders correct toggle text', () => { + expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').text()).toEqual( + 'Show 1 additional field' + ); }); - it('does not render an expand button', () => { - expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(true); + it('renders a collapse icon', () => { + expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(false); }); - it('renders a hidden fields indicator', () => { - expect(wrapper.find('.appSearchResult__hiddenFieldsIndicator').text()).toEqual( - '1 more fields' - ); + it('does not render an expand icon', () => { + expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(true); }); it('shows no more than 5 fields', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx index 76b06754d6ce6..f25eb2a4ba09e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx @@ -5,6 +5,7 @@ */ import React, { useState, useMemo } from 'react'; +import classNames from 'classnames'; import './result.scss'; @@ -49,23 +50,31 @@ export const Result: React.FC = ({ if (schemaForTypeHighlights) return schemaForTypeHighlights[fieldName]; }; + const documentLink = getDocumentDetailRoute(resultMeta.engine, resultMeta.id); const conditionallyLinkedArticle = (children: React.ReactNode) => { return shouldLinkToDetailPage ? ( - - {children} + +
    + {children} +
    ) : (
    {children}
    ); }; + const classes = classNames('appSearchResult', { + 'appSearchResult--link': shouldLinkToDetailPage, + }); + return ( {conditionallyLinkedArticle( @@ -75,53 +84,57 @@ export const Result: React.FC = ({ showScore={!!showScore} isMetaEngine={isMetaEngine} /> -
    - {resultFields - .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) - .map(([field, value]: [string, FieldValue]) => ( - - ))} -
    - {numResults > RESULT_CUTOFF && !isOpen && ( -
    - {i18n.translate('xpack.enterpriseSearch.appSearch.result.numberOfAdditionalFields', { - defaultMessage: '{numberOfAdditionalFields} more fields', - values: { - numberOfAdditionalFields: numResults - RESULT_CUTOFF, - }, - })} -
    - )} + {resultFields + .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) + .map(([field, value]: [string, FieldValue]) => ( + + ))} )} {numResults > RESULT_CUTOFF && ( )} +
    + {shouldLinkToDetailPage && ( + + + + + + )} +
    ); }; From f7961998d9bea29c4c89f9be49f7d2363987525d Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Wed, 23 Dec 2020 12:18:13 -0700 Subject: [PATCH 44/72] Migrates spaces usage collector es client from legacy to new (#86900) --- .../spaces_usage_collector.test.ts | 66 ++++++++++++------- .../spaces_usage_collector.ts | 18 ++--- 2 files changed, 48 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts index ea8770b7843cf..747e37e7db32b 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts @@ -12,7 +12,10 @@ import { ILicense, LicensingPluginSetup } from '../../../licensing/server'; import { UsageStats } from '../usage_stats'; import { usageStatsClientMock } from '../usage_stats/usage_stats_client.mock'; import { usageStatsServiceMock } from '../usage_stats/usage_stats_service.mock'; -import { pluginInitializerContextConfigMock } from 'src/core/server/mocks'; +import { + elasticsearchServiceMock, + pluginInitializerContextConfigMock, +} from 'src/core/server/mocks'; import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks'; interface SetupOpts { @@ -74,31 +77,39 @@ function setup({ }; } -const defaultCallClusterMock = jest.fn().mockResolvedValue({ - hits: { - total: { - value: 2, +const defaultEsClientSearchMock = jest.fn().mockResolvedValue({ + body: { + hits: { + total: { + value: 2, + }, }, - }, - aggregations: { - disabledFeatures: { - buckets: [ - { - key: 'feature1', - doc_count: 1, - }, - ], + aggregations: { + disabledFeatures: { + buckets: [ + { + key: 'feature1', + doc_count: 1, + }, + ], + }, }, }, }); -const getMockFetchContext = (mockedCallCluster: jest.Mock) => { +const getMockFetchContext = (mockedEsClient: any) => { return { ...createCollectorFetchContextMock(), - callCluster: mockedCallCluster, + esClient: mockedEsClient, }; }; +const getMockedEsClient = (esClientMock: jest.Mock) => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.search = esClientMock; + return esClient; +}; + describe('error handling', () => { it('handles a 404 when searching for space usage', async () => { const { features, licensing, usageCollection, usageStatsService } = setup({ @@ -110,8 +121,10 @@ describe('error handling', () => { licensing, usageStatsServicePromise: Promise.resolve(usageStatsService), }); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.search.mockRejectedValue({ status: 404 }); - await collector.fetch(getMockFetchContext(jest.fn().mockRejectedValue({ status: 404 }))); + await collector.fetch(getMockFetchContext(esClient)); }); it('throws error for a non-404', async () => { @@ -124,13 +137,13 @@ describe('error handling', () => { licensing, usageStatsServicePromise: Promise.resolve(usageStatsService), }); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const statusCodes = [401, 402, 403, 500]; for (const statusCode of statusCodes) { const error = { status: statusCode }; - await expect( - collector.fetch(getMockFetchContext(jest.fn().mockRejectedValue(error))) - ).rejects.toBe(error); + esClient.search.mockRejectedValue(error); + await expect(collector.fetch(getMockFetchContext(esClient))).rejects.toBe(error); } }); }); @@ -148,9 +161,10 @@ describe('with a basic license', () => { licensing, usageStatsServicePromise: Promise.resolve(usageStatsService), }); - usageData = await collector.fetch(getMockFetchContext(defaultCallClusterMock)); + const esClient = getMockedEsClient(defaultEsClientSearchMock); + usageData = await collector.fetch(getMockFetchContext(esClient)); - expect(defaultCallClusterMock).toHaveBeenCalledWith('search', { + expect(defaultEsClientSearchMock).toHaveBeenCalledWith({ body: { aggs: { disabledFeatures: { @@ -206,7 +220,9 @@ describe('with no license', () => { licensing, usageStatsServicePromise: Promise.resolve(usageStatsService), }); - usageData = await collector.fetch(getMockFetchContext(defaultCallClusterMock)); + const esClient = getMockedEsClient(defaultEsClientSearchMock); + + usageData = await collector.fetch(getMockFetchContext(esClient)); }); test('sets enabled to false', () => { @@ -245,7 +261,9 @@ describe('with platinum license', () => { licensing, usageStatsServicePromise: Promise.resolve(usageStatsService), }); - usageData = await collector.fetch(getMockFetchContext(defaultCallClusterMock)); + const esClient = getMockedEsClient(defaultEsClientSearchMock); + + usageData = await collector.fetch(getMockFetchContext(esClient)); }); test('sets enabled to true', () => { diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts index 44388453d0707..269490bddd8dc 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts @@ -4,19 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyCallAPIOptions } from 'src/core/server'; +import { ElasticsearchClient } from 'src/core/server'; import { take } from 'rxjs/operators'; import { CollectorFetchContext, UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { Observable } from 'rxjs'; import { PluginsSetup } from '../plugin'; import { UsageStats, UsageStatsServiceSetup } from '../usage_stats'; -type CallCluster = ( - endpoint: string, - clientParams: Record, - options?: LegacyCallAPIOptions -) => Promise; - interface SpacesAggregationResponse { hits: { total: { value: number }; @@ -37,7 +31,7 @@ interface SpacesAggregationResponse { * @return {UsageData} */ async function getSpacesUsage( - callCluster: CallCluster, + esClient: ElasticsearchClient, kibanaIndex: string, features: PluginsSetup['features'], spacesAvailable: boolean @@ -50,7 +44,7 @@ async function getSpacesUsage( let resp: SpacesAggregationResponse | undefined; try { - resp = await callCluster('search', { + ({ body: resp } = await esClient.search({ index: kibanaIndex, body: { track_total_hits: true, @@ -72,7 +66,7 @@ async function getSpacesUsage( }, size: 0, }, - }); + })); } catch (err) { if (err.status === 404) { return null; @@ -208,14 +202,14 @@ export function getSpacesUsageCollector( 'apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.yes': { type: 'long' }, 'apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.no': { type: 'long' }, }, - fetch: async ({ callCluster }: CollectorFetchContext) => { + fetch: async ({ esClient }: CollectorFetchContext) => { const { licensing, kibanaIndexConfig$, features, usageStatsServicePromise } = deps; const license = await licensing.license$.pipe(take(1)).toPromise(); const available = license.isAvailable; // some form of spaces is available for all valid licenses const kibanaIndex = (await kibanaIndexConfig$.pipe(take(1)).toPromise()).kibana.index; - const usageData = await getSpacesUsage(callCluster, kibanaIndex, features, available); + const usageData = await getSpacesUsage(esClient, kibanaIndex, features, available); const usageStats = await getUsageStats(usageStatsServicePromise, available); return { From cf6afe04adc0a52ce5b89e01004512870f0aa7d3 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Wed, 23 Dec 2020 14:25:34 -0500 Subject: [PATCH 45/72] [Uptime] Fix/85236 user experience display low values (#86026) * add hasVitals prop to CoreVitalItem * pass hasVitals prop to CoreVitalsItem based on coreVitalPages * adjust criteria for displaying no core vital item data * add stories for CoreVitalItem edge cases * remove comment from core web vitals index page * update test comment in CoreVitalItem * adjust APM get_web_core_vitals endpoint to return a number for cls value, and adjust corresponding observability components * remove hasVitals from CoreVitalItem props and adjust storybook stories * add comment to EuiStat aria-label in CoreVitalItem * adjust CoreVitalItem tests * adjust APM KeyUXMetrics test * adjust APM get_web_core_vitals endpoint to return null for cls when cls is undefined * adjust unit and integration tests that rely on apm get_web_core_vitals * add comment in get_web_core_vitals * update CLS value in Observability core_web_vitals index * add withKibanaIntl to CoreVitalItem test to wrap in Intl Provider and KibanaReact provider * update CoreVitalItem test to use testing-library/react test_helper Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../UXMetrics/__tests__/KeyUXMetrics.test.tsx | 2 +- .../lib/rum_client/get_web_core_vitals.ts | 4 +- .../components/app/section/ux/index.test.tsx | 2 +- .../app/section/ux/mock_data/ux.mock.ts | 2 +- .../__stories__/core_vitals.stories.tsx | 15 ++++- .../core_web_vitals/core_vital_item.test.tsx | 67 +++++++++++++++++++ .../core_web_vitals/core_vital_item.tsx | 4 +- .../shared/core_web_vitals/index.tsx | 22 +++--- .../observability/public/data_handler.test.ts | 4 +- .../trial/tests/csm/web_core_vitals.ts | 2 +- 10 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.test.tsx diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx index baa9cb7dd74f9..5d73cbc4cd3c8 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx @@ -23,7 +23,7 @@ describe('KeyUXMetrics', () => { { expect(getByText('Largest contentful paint')).toBeInTheDocument(); expect(getByText('1.94 s')).toBeInTheDocument(); expect(getByText('14 ms')).toBeInTheDocument(); - expect(getByText('0.01')).toBeInTheDocument(); + expect(getByText('0.010')).toBeInTheDocument(); // LCP Rank Values expect(getByText('Good (65%)')).toBeInTheDocument(); diff --git a/x-pack/plugins/observability/public/components/app/section/ux/mock_data/ux.mock.ts b/x-pack/plugins/observability/public/components/app/section/ux/mock_data/ux.mock.ts index 017f385d36735..bbe81699e999d 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/mock_data/ux.mock.ts +++ b/x-pack/plugins/observability/public/components/app/section/ux/mock_data/ux.mock.ts @@ -9,7 +9,7 @@ import { UxFetchDataResponse } from '../../../../../typings'; export const response: UxFetchDataResponse = { appLink: '/app/ux', coreWebVitals: { - cls: '0.01', + cls: 0.01, fid: 13.5, lcp: 1942.6666666666667, tbt: 281.55833333333334, diff --git a/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx b/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx index 26cf9c144b4a1..208c840b403e9 100644 --- a/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx +++ b/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx @@ -33,13 +33,26 @@ export default { ], }; -export function Basic() { +export function NoDataAvailable() { + return ( + + ); +} + +export function OneHundredPercentGood() { return ( ); diff --git a/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.test.tsx b/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.test.tsx new file mode 100644 index 0000000000000..346355e11c6ef --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.test.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { render } from '../../../utils/test_helper'; +import { CoreVitalItem } from './core_vital_item'; +import { NO_DATA } from './translations'; + +describe('CoreVitalItem', () => { + const value = '0.005'; + const title = 'Cumulative Layout Shift'; + const thresholds = { bad: '0.25', good: '0.1' }; + const loading = false; + const helpLabel = 'sample help label'; + + it('renders if value is truthy', () => { + const { getByText } = render( + + ); + + expect(getByText(title)).toBeInTheDocument(); + expect(getByText(value)).toBeInTheDocument(); + expect(getByText('Good (85%)')).toBeInTheDocument(); + expect(getByText('Needs improvement (10%)')).toBeInTheDocument(); + expect(getByText('Poor (5%)')).toBeInTheDocument(); + }); + + it('renders loading state when loading is truthy', () => { + const { queryByText, getByText } = render( + + ); + + expect(queryByText(value)).not.toBeInTheDocument(); + expect(getByText('--')).toBeInTheDocument(); + }); + + it('renders no data UI if value is falsey and loading is falsey', () => { + const { getByText } = render( + + ); + + expect(getByText(NO_DATA)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.tsx b/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.tsx index 18831565b8784..23dd0b86a235b 100644 --- a/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.tsx +++ b/x-pack/plugins/observability/public/components/shared/core_web_vitals/core_vital_item.tsx @@ -88,12 +88,14 @@ export function CoreVitalItem({ const biggestValIndex = ranks.indexOf(Math.max(...ranks)); - if ((value === null || value !== undefined) && ranks[0] === 100 && !loading) { + if (!value && !loading) { return ; } + return ( <> { title: 'User Experience', appLink: '/ux', coreWebVitals: { - cls: '0.01', + cls: 0.01, fid: 5, lcp: 1464.3333333333333, tbt: 232.92166666666665, @@ -298,7 +298,7 @@ describe('registerDataHandler', () => { title: 'User Experience', appLink: '/ux', coreWebVitals: { - cls: '0.01', + cls: 0.01, fid: 5, lcp: 1464.3333333333333, tbt: 232.92166666666665, diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts b/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts index 7e970493eb611..50c261d2d37ad 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts @@ -49,7 +49,7 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) expectSnapshot(response.body).toMatchInline(` Object { - "cls": "0.000", + "cls": 0, "clsRanks": Array [ 100, 0, From a5cfc7fb4a7ce0363b73a118299e228e925814df Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 23 Dec 2020 21:13:03 +0100 Subject: [PATCH 46/72] [Lens] Add percentile function (#86490) --- .../dimension_panel/dimension_panel.test.tsx | 2 + .../indexpattern.test.ts | 47 ++++ .../calculations/moving_average.tsx | 17 +- .../definitions/date_histogram.test.tsx | 6 +- .../definitions/filters/filters.test.tsx | 3 +- .../operations/definitions/helpers.tsx | 28 ++- .../operations/definitions/index.ts | 25 +- .../definitions/last_value.test.tsx | 3 +- .../operations/definitions/metrics.tsx | 29 ++- .../definitions/percentile.test.tsx | 237 ++++++++++++++++++ .../operations/definitions/percentile.tsx | 189 ++++++++++++++ .../definitions/ranges/advanced_editor.tsx | 4 +- .../definitions/ranges/ranges.test.tsx | 12 +- .../operations/definitions/ranges/ranges.tsx | 10 +- .../operations/definitions/terms/index.tsx | 15 +- .../definitions/terms/terms.test.tsx | 45 +++- .../operations/operations.test.ts | 5 + .../indexpattern_datasource/to_expression.ts | 10 +- 18 files changed, 631 insertions(+), 56 deletions(-) create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 95a6c351e1fc2..5d477d98d042d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -1329,8 +1329,10 @@ describe('IndexPatternDimensionEditorPanel', () => { 'Median', 'Minimum', 'Moving average', + 'Percentile', 'Sum', 'Unique count', + '\u00a0', ]); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 7e67d863346c7..1f23fd3830477 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -474,6 +474,53 @@ describe('IndexPattern Data Source', () => { expect(ast.chain[0].arguments.timeFields).toEqual(['timestamp', 'another_datefield']); }); + it('should add the suffix to the remap column id if provided by the operation', async () => { + const queryBaseState: IndexPatternBaseState = { + currentIndexPatternId: '1', + layers: { + first: { + indexPatternId: '1', + columnOrder: ['def', 'abc'], + columns: { + abc: { + label: '23rd percentile', + dataType: 'number', + isBucketed: false, + sourceField: 'bytes', + operationType: 'percentile', + params: { + percentile: 23, + }, + }, + def: { + label: 'Terms', + dataType: 'string', + isBucketed: true, + operationType: 'terms', + sourceField: 'source', + params: { + size: 5, + orderBy: { + type: 'alphabetical', + }, + orderDirection: 'asc', + }, + }, + }, + }, + }, + }; + + const state = enrichBaseState(queryBaseState); + + const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + expect(Object.keys(JSON.parse(ast.chain[1].arguments.idMap[0] as string))).toEqual([ + 'col-0-def', + // col-1 is the auto naming of esasggs, abc is the specified column id, .23 is the generated suffix + 'col-1-abc.23', + ]); + }); + it('should add time_scale and format function if time scale is set and supported', async () => { const queryBaseState: IndexPatternBaseState = { currentIndexPatternId: '1', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx index e1bc378635f1d..d9805b337c000 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx @@ -19,7 +19,7 @@ import { hasDateField, } from './utils'; import { updateColumnParam } from '../../layer_helpers'; -import { useDebounceWithOptions } from '../helpers'; +import { isValidNumber, useDebounceWithOptions } from '../helpers'; import { adjustTimeScaleOnOtherColumnChange } from '../../time_scale_utils'; import type { OperationDefinition, ParamEditorProps } from '..'; @@ -122,19 +122,6 @@ export const movingAverageOperation: OperationDefinition< timeScalingMode: 'optional', }; -function isValidNumber(input: string) { - if (input === '') return false; - try { - const val = parseFloat(input); - if (isNaN(val)) return false; - if (val < 1) return false; - if (val.toString().includes('.')) return false; - } catch (e) { - return false; - } - return true; -} - function MovingAverageParamEditor({ layer, updateLayer, @@ -145,7 +132,7 @@ function MovingAverageParamEditor({ useDebounceWithOptions( () => { - if (!isValidNumber(inputValue)) return; + if (!isValidNumber(inputValue, true, undefined, 1)) return; const inputNumber = parseInt(inputValue, 10); updateLayer( updateColumnParam({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx index 8c2fa43b541d4..abd033c0db4cf 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx @@ -199,7 +199,8 @@ describe('date_histogram', () => { const esAggsFn = dateHistogramOperation.toEsAggsFn( layer.columns.col1 as DateHistogramIndexPatternColumn, 'col1', - indexPattern1 + indexPattern1, + layer ); expect(esAggsFn).toEqual( expect.objectContaining({ @@ -250,7 +251,8 @@ describe('date_histogram', () => { }, }, ]), - } + }, + layer ); expect(esAggsFn).toEqual( expect.objectContaining({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.test.tsx index cf57c35f6f68b..86767fbc8b469 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.test.tsx @@ -83,7 +83,8 @@ describe('filters', () => { const esAggsFn = filtersOperation.toEsAggsFn( layer.columns.col1 as FiltersIndexPatternColumn, 'col1', - createMockedIndexPattern() + createMockedIndexPattern(), + layer ); expect(esAggsFn).toEqual( expect.objectContaining({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx index 7b96bcf4f2069..29148052cee8e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx @@ -7,7 +7,7 @@ import { useRef } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import { i18n } from '@kbn/i18n'; -import { operationDefinitionMap } from '.'; +import { IndexPatternColumn, operationDefinitionMap } from '.'; import { FieldBasedIndexPatternColumn } from './column_types'; import { IndexPattern } from '../../types'; @@ -63,6 +63,13 @@ export function getInvalidFieldMessage( : undefined; } +export function getEsAggsSuffix(column: IndexPatternColumn) { + const operationDefinition = operationDefinitionMap[column.operationType]; + return operationDefinition.input === 'field' && operationDefinition.getEsAggsSuffix + ? operationDefinition.getEsAggsSuffix(column) + : ''; +} + export function getSafeName(name: string, indexPattern: IndexPattern): string { const field = indexPattern.getFieldByName(name); return field @@ -71,3 +78,22 @@ export function getSafeName(name: string, indexPattern: IndexPattern): string { defaultMessage: 'Missing field', }); } + +export function isValidNumber( + inputValue: string | number | null | undefined, + integer?: boolean, + upperBound?: number, + lowerBound?: number +) { + const inputValueAsNumber = Number(inputValue); + return ( + inputValue !== '' && + inputValue !== null && + inputValue !== undefined && + !Number.isNaN(inputValueAsNumber) && + Number.isFinite(inputValueAsNumber) && + (!integer || Number.isInteger(inputValueAsNumber)) && + (upperBound === undefined || inputValueAsNumber <= upperBound) && + (lowerBound === undefined || inputValueAsNumber >= lowerBound) + ); +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 6231460347de2..36c9cf75d2b6c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -9,6 +9,7 @@ import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { termsOperation, TermsIndexPatternColumn } from './terms'; import { filtersOperation, FiltersIndexPatternColumn } from './filters'; import { cardinalityOperation, CardinalityIndexPatternColumn } from './cardinality'; +import { percentileOperation, PercentileIndexPatternColumn } from './percentile'; import { minOperation, MinIndexPatternColumn, @@ -58,6 +59,7 @@ export type IndexPatternColumn = | CardinalityIndexPatternColumn | SumIndexPatternColumn | MedianIndexPatternColumn + | PercentileIndexPatternColumn | CountIndexPatternColumn | LastValueIndexPatternColumn | CumulativeSumIndexPatternColumn @@ -82,6 +84,7 @@ const internalOperationDefinitions = [ cardinalityOperation, sumOperation, medianOperation, + percentileOperation, lastValueOperation, countOperation, rangeOperation, @@ -96,6 +99,7 @@ export { rangeOperation } from './ranges'; export { filtersOperation } from './filters'; export { dateHistogramOperation } from './date_histogram'; export { minOperation, averageOperation, sumOperation, maxOperation } from './metrics'; +export { percentileOperation } from './percentile'; export { countOperation } from './count'; export { lastValueOperation } from './last_value'; export { @@ -223,7 +227,12 @@ interface FieldlessOperationDefinition { * Function turning a column into an agg config passed to the `esaggs` function * together with the agg configs returned from other columns. */ - toEsAggsFn: (column: C, columnId: string, indexPattern: IndexPattern) => ExpressionAstFunction; + toEsAggsFn: ( + column: C, + columnId: string, + indexPattern: IndexPattern, + layer: IndexPatternLayer + ) => ExpressionAstFunction; } interface FieldBasedOperationDefinition { @@ -262,7 +271,19 @@ interface FieldBasedOperationDefinition { * Function turning a column into an agg config passed to the `esaggs` function * together with the agg configs returned from other columns. */ - toEsAggsFn: (column: C, columnId: string, indexPattern: IndexPattern) => ExpressionAstFunction; + toEsAggsFn: ( + column: C, + columnId: string, + indexPattern: IndexPattern, + layer: IndexPatternLayer + ) => ExpressionAstFunction; + /** + * Optional function to return the suffix used for ES bucket paths and esaggs column id. + * This is relevant for multi metrics to pick the right value. + * + * @param column The current column + */ + getEsAggsSuffix?: (column: C) => string; /** * Validate that the operation has the right preconditions in the state. For example: * diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx index bde8cd0e42427..96b12a714e613 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx @@ -69,7 +69,8 @@ describe('last_value', () => { const esAggsFn = lastValueOperation.toEsAggsFn( { ...lastValueColumn, params: { ...lastValueColumn.params } }, 'col1', - {} as IndexPattern + {} as IndexPattern, + layer ); expect(esAggsFn).toEqual( expect.objectContaining({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index eb25b5d932b1f..470a5407b2589 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -83,21 +83,24 @@ function buildMetricOperation>({ }, onOtherColumnChanged: (layer, thisColumnId, changedColumnId) => optionalTimeScaling - ? adjustTimeScaleOnOtherColumnChange(layer, thisColumnId, changedColumnId) - : layer.columns[thisColumnId], + ? (adjustTimeScaleOnOtherColumnChange(layer, thisColumnId, changedColumnId) as T) + : (layer.columns[thisColumnId] as T), getDefaultLabel: (column, indexPattern, columns) => labelLookup(getSafeName(column.sourceField, indexPattern), column), - buildColumn: ({ field, previousColumn }) => ({ - label: labelLookup(field.displayName, previousColumn), - dataType: 'number', - operationType: type, - sourceField: field.name, - isBucketed: false, - scale: 'ratio', - timeScale: optionalTimeScaling ? previousColumn?.timeScale : undefined, - params: - previousColumn && previousColumn.dataType === 'number' ? previousColumn.params : undefined, - }), + buildColumn: ({ field, previousColumn }) => + ({ + label: labelLookup(field.displayName, previousColumn), + dataType: 'number', + operationType: type, + sourceField: field.name, + isBucketed: false, + scale: 'ratio', + timeScale: optionalTimeScaling ? previousColumn?.timeScale : undefined, + params: + previousColumn && previousColumn.dataType === 'number' + ? previousColumn.params + : undefined, + } as T), onFieldChange: (oldColumn, field) => { return { ...oldColumn, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx new file mode 100644 index 0000000000000..c22eec62ea1ab --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx @@ -0,0 +1,237 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { createMockedIndexPattern } from '../../mocks'; +import { percentileOperation } from './index'; +import { IndexPattern, IndexPatternLayer } from '../../types'; +import { PercentileIndexPatternColumn } from './percentile'; +import { EuiFieldNumber } from '@elastic/eui'; +import { act } from 'react-dom/test-utils'; +import { EuiFormRow } from '@elastic/eui'; + +const defaultProps = { + storage: {} as IStorageWrapper, + uiSettings: {} as IUiSettingsClient, + savedObjectsClient: {} as SavedObjectsClientContract, + dateRange: { fromDate: 'now-1d', toDate: 'now' }, + data: dataPluginMock.createStartContract(), + http: {} as HttpSetup, + indexPattern: { + ...createMockedIndexPattern(), + hasRestrictions: false, + } as IndexPattern, +}; + +describe('percentile', () => { + let layer: IndexPatternLayer; + const InlineOptions = percentileOperation.paramEditor!; + + beforeEach(() => { + layer = { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: { + label: 'Top value of category', + dataType: 'string', + isBucketed: true, + operationType: 'terms', + params: { + orderBy: { type: 'alphabetical' }, + size: 3, + orderDirection: 'asc', + }, + sourceField: 'category', + }, + col2: { + label: '23rd percentile of a', + dataType: 'number', + isBucketed: false, + sourceField: 'a', + operationType: 'percentile', + params: { + percentile: 23, + }, + }, + }, + }; + }); + + describe('toEsAggsFn', () => { + it('should reflect params correctly', () => { + const percentileColumn = layer.columns.col2 as PercentileIndexPatternColumn; + const esAggsFn = percentileOperation.toEsAggsFn( + percentileColumn, + 'col1', + {} as IndexPattern, + layer + ); + expect(esAggsFn).toEqual( + expect.objectContaining({ + arguments: expect.objectContaining({ + percents: [23], + field: ['a'], + }), + }) + ); + }); + }); + + describe('onFieldChange', () => { + it('should change correctly to new field', () => { + const oldColumn: PercentileIndexPatternColumn = { + operationType: 'percentile', + sourceField: 'bytes', + label: '23rd percentile of bytes', + isBucketed: true, + dataType: 'number', + params: { + percentile: 23, + }, + }; + const indexPattern = createMockedIndexPattern(); + const newNumberField = indexPattern.getFieldByName('memory')!; + const column = percentileOperation.onFieldChange(oldColumn, newNumberField); + + expect(column).toEqual( + expect.objectContaining({ + dataType: 'number', + sourceField: 'memory', + params: expect.objectContaining({ + percentile: 23, + }), + }) + ); + expect(column.label).toContain('memory'); + }); + }); + + describe('buildColumn', () => { + it('should set default percentile', () => { + const indexPattern = createMockedIndexPattern(); + const bytesField = indexPattern.fields.find(({ name }) => name === 'bytes')!; + bytesField.displayName = 'test'; + const percentileColumn = percentileOperation.buildColumn({ + indexPattern, + field: bytesField, + layer: { columns: {}, columnOrder: [], indexPatternId: '' }, + }); + expect(percentileColumn.dataType).toEqual('number'); + expect(percentileColumn.params.percentile).toEqual(95); + expect(percentileColumn.label).toEqual('95th percentile of test'); + }); + }); + + describe('param editor', () => { + it('should render current percentile', () => { + const updateLayerSpy = jest.fn(); + const instance = shallow( + + ); + + const input = instance.find('[data-test-subj="lns-indexPattern-percentile-input"]'); + + expect(input.prop('value')).toEqual('23'); + }); + + it('should update state on change', async () => { + jest.useFakeTimers(); + const updateLayerSpy = jest.fn(); + const instance = mount( + + ); + + jest.runAllTimers(); + + const input = instance + .find('[data-test-subj="lns-indexPattern-percentile-input"]') + .find(EuiFieldNumber); + + await act(async () => { + input.prop('onChange')!({ target: { value: '27' } } as React.ChangeEvent); + }); + + instance.update(); + + jest.runAllTimers(); + + expect(updateLayerSpy).toHaveBeenCalledWith({ + ...layer, + columns: { + ...layer.columns, + col2: { + ...layer.columns.col2, + params: { + percentile: 27, + }, + label: '27th percentile of a', + }, + }, + }); + }); + + it('should not update on invalid input, but show invalid value locally', async () => { + const updateLayerSpy = jest.fn(); + const instance = mount( + + ); + + jest.runAllTimers(); + + const input = instance + .find('[data-test-subj="lns-indexPattern-percentile-input"]') + .find(EuiFieldNumber); + + await act(async () => { + input.prop('onChange')!({ + target: { value: '12.12' }, + } as React.ChangeEvent); + }); + + instance.update(); + + jest.runAllTimers(); + + expect(updateLayerSpy).not.toHaveBeenCalled(); + + expect( + instance + .find('[data-test-subj="lns-indexPattern-percentile-form"]') + .find(EuiFormRow) + .prop('isInvalid') + ).toEqual(true); + expect( + instance + .find('[data-test-subj="lns-indexPattern-percentile-input"]') + .find(EuiFieldNumber) + .prop('value') + ).toEqual('12.12'); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx new file mode 100644 index 0000000000000..b381a0ecb664a --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { AggFunctionsMapping } from 'src/plugins/data/public'; +import { buildExpressionFunction } from '../../../../../../../src/plugins/expressions/public'; +import { OperationDefinition } from './index'; +import { + getInvalidFieldMessage, + getSafeName, + isValidNumber, + useDebounceWithOptions, +} from './helpers'; +import { FieldBasedIndexPatternColumn } from './column_types'; + +export interface PercentileIndexPatternColumn extends FieldBasedIndexPatternColumn { + operationType: 'percentile'; + params: { + percentile: number; + format?: { + id: string; + params?: { + decimals: number; + }; + }; + }; +} + +function ofName(name: string, percentile: number) { + return i18n.translate('xpack.lens.indexPattern.percentileOf', { + defaultMessage: + '{percentile, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} percentile of {name}', + values: { name, percentile }, + }); +} + +const DEFAULT_PERCENTILE_VALUE = 95; + +export const percentileOperation: OperationDefinition = { + type: 'percentile', + displayName: i18n.translate('xpack.lens.indexPattern.percentile', { + defaultMessage: 'Percentile', + }), + input: 'field', + getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type: fieldType }) => { + if (fieldType === 'number' && aggregatable && !aggregationRestrictions) { + return { + dataType: 'number', + isBucketed: false, + scale: 'ratio', + }; + } + }, + isTransferable: (column, newIndexPattern) => { + const newField = newIndexPattern.getFieldByName(column.sourceField); + + return Boolean( + newField && + newField.type === 'number' && + newField.aggregatable && + !newField.aggregationRestrictions + ); + }, + getDefaultLabel: (column, indexPattern, columns) => + ofName(getSafeName(column.sourceField, indexPattern), column.params.percentile), + buildColumn: ({ field, previousColumn, indexPattern }) => { + const existingFormat = + previousColumn?.params && 'format' in previousColumn?.params + ? previousColumn?.params?.format + : undefined; + const existingPercentileParam = + previousColumn?.operationType === 'percentile' && previousColumn?.params.percentile; + const newPercentileParam = existingPercentileParam || DEFAULT_PERCENTILE_VALUE; + return { + label: ofName(getSafeName(field.name, indexPattern), newPercentileParam), + dataType: 'number', + operationType: 'percentile', + sourceField: field.name, + isBucketed: false, + scale: 'ratio', + params: { + format: existingFormat, + percentile: newPercentileParam, + }, + }; + }, + onFieldChange: (oldColumn, field) => { + return { + ...oldColumn, + label: ofName(field.displayName, oldColumn.params.percentile), + sourceField: field.name, + }; + }, + toEsAggsFn: (column, columnId, _indexPattern) => { + return buildExpressionFunction('aggPercentiles', { + id: columnId, + enabled: true, + schema: 'metric', + field: column.sourceField, + percents: [column.params.percentile], + }).toAst(); + }, + getEsAggsSuffix: (column) => { + const value = column.params.percentile; + return `.${value}`; + }, + getErrorMessage: (layer, columnId, indexPattern) => + getInvalidFieldMessage(layer.columns[columnId] as FieldBasedIndexPatternColumn, indexPattern), + paramEditor: function PercentileParamEditor({ + layer, + updateLayer, + currentColumn, + columnId, + indexPattern, + }) { + const [inputValue, setInputValue] = useState(String(currentColumn.params.percentile)); + + const inputValueAsNumber = Number(inputValue); + // an input is value if it's not an empty string, parses to a valid number, is between 0 and 100 (exclusive) + // and is an integer + const inputValueIsValid = isValidNumber(inputValue, true, 99, 1); + + useDebounceWithOptions( + () => { + if (!inputValueIsValid) return; + updateLayer({ + ...layer, + columns: { + ...layer.columns, + [columnId]: { + ...currentColumn, + label: currentColumn.customLabel + ? currentColumn.label + : ofName( + indexPattern.getFieldByName(currentColumn.sourceField)?.displayName || + currentColumn.sourceField, + inputValueAsNumber + ), + params: { + ...currentColumn.params, + percentile: inputValueAsNumber, + }, + }, + }, + }); + }, + { skipFirstRender: true }, + 256, + [inputValue] + ); + + const handleInputChange = useCallback((e: React.ChangeEvent) => { + const val = String(e.target.value); + setInputValue(val); + }, []); + return ( + + + + ); + }, +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx index 9ab677bf68f62..420846f7fc801 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx @@ -22,7 +22,7 @@ import { keys, } from '@elastic/eui'; import { IFieldFormat } from '../../../../../../../../src/plugins/data/common'; -import { RangeTypeLens, isValidRange, isValidNumber } from './ranges'; +import { RangeTypeLens, isValidRange } from './ranges'; import { FROM_PLACEHOLDER, TO_PLACEHOLDER, TYPING_DEBOUNCE_TIME } from './constants'; import { NewBucketButton, @@ -30,7 +30,7 @@ import { DraggableBucketContainer, LabelInput, } from '../shared_components'; -import { useDebounceWithOptions } from '../helpers'; +import { isValidNumber, useDebounceWithOptions } from '../helpers'; const generateId = htmlIdGenerator(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx index c2c52985c6cd2..987c8971aa310 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -142,7 +142,8 @@ describe('ranges', () => { const esAggsFn = rangeOperation.toEsAggsFn( layer.columns.col1 as RangeIndexPatternColumn, 'col1', - {} as IndexPattern + {} as IndexPattern, + layer ); expect(esAggsFn).toMatchInlineSnapshot(` Object { @@ -184,7 +185,8 @@ describe('ranges', () => { const esAggsFn = rangeOperation.toEsAggsFn( layer.columns.col1 as RangeIndexPatternColumn, 'col1', - {} as IndexPattern + {} as IndexPattern, + layer ); expect(esAggsFn).toEqual( @@ -203,7 +205,8 @@ describe('ranges', () => { const esAggsFn = rangeOperation.toEsAggsFn( layer.columns.col1 as RangeIndexPatternColumn, 'col1', - {} as IndexPattern + {} as IndexPattern, + layer ); expect(esAggsFn).toEqual( @@ -222,7 +225,8 @@ describe('ranges', () => { const esAggsFn = rangeOperation.toEsAggsFn( layer.columns.col1 as RangeIndexPatternColumn, 'col1', - {} as IndexPattern + {} as IndexPattern, + layer ); expect((esAggsFn as { arguments: unknown }).arguments).toEqual( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index b687e6fe3da50..aa5cc8255a584 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -19,7 +19,7 @@ import { updateColumnParam } from '../../layer_helpers'; import { supportedFormats } from '../../../format_column'; import { MODES, AUTO_BARS, DEFAULT_INTERVAL, MIN_HISTOGRAM_BARS, SLICES } from './constants'; import { IndexPattern, IndexPatternField } from '../../../types'; -import { getInvalidFieldMessage } from '../helpers'; +import { getInvalidFieldMessage, isValidNumber } from '../helpers'; type RangeType = Omit; // Try to cover all possible serialized states for ranges @@ -52,10 +52,6 @@ export type UpdateParamsFnType = ( value: RangeColumnParams[K] ) => void; -// on initialization values can be null (from the Infinity serialization), so handle it correctly -// or they will be casted to 0 by the editor ( see #78867 ) -export const isValidNumber = (value: number | '' | null): value is number => - value != null && value !== '' && !isNaN(value) && isFinite(value); export const isRangeWithin = (range: RangeType): boolean => range.from <= range.to; const isFullRange = (range: RangeTypeLens): range is FullRangeTypeLens => isValidNumber(range.from) && isValidNumber(range.to); @@ -152,10 +148,10 @@ export const rangeOperation: OperationDefinition = { label: range.label }; // be careful with the fields to set on partial ranges if (isValidNumber(range.from)) { - partialRange.from = range.from; + partialRange.from = Number(range.from); } if (isValidNumber(range.to)) { - partialRange.to = range.to; + partialRange.to = Number(range.to); } return partialRange; }) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index 8462d374c6e6b..625084000fa93 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -23,7 +23,7 @@ import { DataType } from '../../../../types'; import { OperationDefinition } from '../index'; import { FieldBasedIndexPatternColumn } from '../column_types'; import { ValuesRangeInput } from './values_range_input'; -import { getInvalidFieldMessage } from '../helpers'; +import { getEsAggsSuffix, getInvalidFieldMessage } from '../helpers'; import type { IndexPatternLayer } from '../../../types'; function ofName(name?: string) { @@ -119,7 +119,10 @@ export const termsOperation: OperationDefinition { + toEsAggsFn: (column, columnId, _indexPattern, layer) => { return buildExpressionFunction('aggTerms', { id: columnId, enabled: true, schema: 'segment', field: column.sourceField, orderBy: - column.params.orderBy.type === 'alphabetical' ? '_key' : column.params.orderBy.columnId, + column.params.orderBy.type === 'alphabetical' + ? '_key' + : `${column.params.orderBy.columnId}${getEsAggsSuffix( + layer.columns[column.params.orderBy.columnId] + )}`, order: column.params.orderDirection, size: column.params.size, otherBucket: Boolean(column.params.otherBucket), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index 0209c0b9a448b..d60992bda2e2a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -65,7 +65,8 @@ describe('terms', () => { const esAggsFn = termsOperation.toEsAggsFn( { ...termsColumn, params: { ...termsColumn.params, otherBucket: true } }, 'col1', - {} as IndexPattern + {} as IndexPattern, + layer ); expect(esAggsFn).toEqual( expect.objectContaining({ @@ -87,7 +88,8 @@ describe('terms', () => { params: { ...termsColumn.params, otherBucket: false, missingBucket: true }, }, 'col1', - {} as IndexPattern + {} as IndexPattern, + layer ); expect(esAggsFn).toEqual( expect.objectContaining({ @@ -98,6 +100,45 @@ describe('terms', () => { }) ); }); + + it('should include esaggs suffix from other columns in orderby argument', () => { + const termsColumn = layer.columns.col1 as TermsIndexPatternColumn; + const esAggsFn = termsOperation.toEsAggsFn( + { + ...termsColumn, + params: { + ...termsColumn.params, + otherBucket: true, + orderBy: { type: 'column', columnId: 'abcde' }, + }, + }, + 'col1', + {} as IndexPattern, + { + ...layer, + columns: { + ...layer.columns, + abcde: { + dataType: 'number', + isBucketed: false, + operationType: 'percentile', + sourceField: 'abc', + label: '', + params: { + percentile: 12, + }, + }, + }, + } + ); + expect(esAggsFn).toEqual( + expect.objectContaining({ + arguments: expect.objectContaining({ + orderBy: ['abcde.12'], + }), + }) + ); + }); }); describe('onFieldChange', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts index 9f2b8eab4e09b..882252132c5b3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts @@ -293,6 +293,11 @@ describe('getOperationTypesForField', () => { "operationType": "median", "type": "field", }, + Object { + "field": "bytes", + "operationType": "percentile", + "type": "field", + }, Object { "field": "bytes", "operationType": "last_value", diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts index a5ce4dfbea371..38f51f24aae7d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts @@ -20,6 +20,7 @@ import { operationDefinitionMap } from './operations'; import { IndexPattern, IndexPatternPrivateState, IndexPatternLayer } from './types'; import { OriginalColumn } from './rename_columns'; import { dateHistogramOperation } from './operations/definitions'; +import { getEsAggsSuffix } from './operations/definitions/helpers'; function getExpressionForLayer( layer: IndexPatternLayer, @@ -41,15 +42,20 @@ function getExpressionForLayer( expressions.push(...def.toExpression(layer, colId, indexPattern)); } else { aggs.push( - buildExpression({ type: 'expression', chain: [def.toEsAggsFn(col, colId, indexPattern)] }) + buildExpression({ + type: 'expression', + chain: [def.toEsAggsFn(col, colId, indexPattern, layer)], + }) ); } }); const idMap = columnEntries.reduce((currentIdMap, [colId, column], index) => { + const esAggsId = `col-${columnEntries.length === 1 ? 0 : index}-${colId}`; + const suffix = getEsAggsSuffix(column); return { ...currentIdMap, - [`col-${columnEntries.length === 1 ? 0 : index}-${colId}`]: { + [`${esAggsId}${suffix}`]: { ...column, id: colId, }, From 0ffb9e72edfcfa71fe045a639ad7660f49e0dac9 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Wed, 23 Dec 2020 19:25:21 -0500 Subject: [PATCH 47/72] [Security Solution][Exceptions][Tech Debt] - Refactor exceptions api file to follow value lists pattern (#86903) ## Summary Currently working on issues related to exceptions and it was noted on a separate PR that the request payload validation being done in the client side API calls was unnecessary. It was helpful in development, but not of any added value in production. Not only that, but the extra validations also add to the performance hit. Removed the payload validation and formatted the code to follow the same pattern as that in the value lists api file. Tested that exceptions flows not affected by testing out exceptions CRUD flows. --- .../lists/public/exceptions/api.test.ts | 166 +---- x-pack/plugins/lists/public/exceptions/api.ts | 600 +++++++++--------- 2 files changed, 312 insertions(+), 454 deletions(-) diff --git a/x-pack/plugins/lists/public/exceptions/api.test.ts b/x-pack/plugins/lists/public/exceptions/api.test.ts index 7570e1f050abb..96d5fbf010f42 100644 --- a/x-pack/plugins/lists/public/exceptions/api.test.ts +++ b/x-pack/plugins/lists/public/exceptions/api.test.ts @@ -11,12 +11,6 @@ import { getCreateExceptionListItemSchemaMock } from '../../common/schemas/reque import { getFoundExceptionListItemSchemaMock } from '../../common/schemas/response/found_exception_list_item_schema.mock'; import { getUpdateExceptionListItemSchemaMock } from '../../common/schemas/request/update_exception_list_item_schema.mock'; import { getUpdateExceptionListSchemaMock } from '../../common/schemas/request/update_exception_list_schema.mock'; -import { - CreateExceptionListItemSchema, - CreateExceptionListSchema, - ExceptionListItemSchema, - ExceptionListSchema, -} from '../../common/schemas'; import { getFoundExceptionListSchemaMock } from '../../common/schemas/response/found_exception_list_schema.mock'; import { @@ -33,7 +27,6 @@ import { updateExceptionList, updateExceptionListItem, } from './api'; -import { ApiCallByIdProps, ApiCallByListIdProps, ApiCallFetchExceptionListsProps } from './types'; const abortCtrl = new AbortController(); @@ -75,20 +68,6 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse).toEqual(getExceptionListSchemaMock()); }); - test('it returns error and does not make request if request payload fails decode', async () => { - const payload: Omit & { - description?: string[]; - } = { ...getCreateExceptionListSchemaMock(), description: ['123'] }; - - await expect( - addExceptionList({ - http: httpMock, - list: (payload as unknown) as ExceptionListSchema, - signal: abortCtrl.signal, - }) - ).rejects.toEqual('Invalid value "["123"]" supplied to "description"'); - }); - test('it returns error if response payload fails decode', async () => { const payload = getCreateExceptionListSchemaMock(); const badPayload = getExceptionListSchemaMock(); @@ -102,7 +81,7 @@ describe('Exceptions Lists API', () => { list: payload, signal: abortCtrl.signal, }) - ).rejects.toEqual('Invalid value "undefined" supplied to "id"'); + ).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"')); }); }); @@ -137,20 +116,6 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock()); }); - test('it returns error and does not make request if request payload fails decode', async () => { - const payload: Omit & { - description?: string[]; - } = { ...getCreateExceptionListItemSchemaMock(), description: ['123'] }; - - await expect( - addExceptionListItem({ - http: httpMock, - listItem: (payload as unknown) as ExceptionListItemSchema, - signal: abortCtrl.signal, - }) - ).rejects.toEqual('Invalid value "["123"]" supplied to "description"'); - }); - test('it returns error if response payload fails decode', async () => { const payload = getCreateExceptionListItemSchemaMock(); const badPayload = getExceptionListItemSchemaMock(); @@ -164,7 +129,7 @@ describe('Exceptions Lists API', () => { listItem: payload, signal: abortCtrl.signal, }) - ).rejects.toEqual('Invalid value "undefined" supplied to "id"'); + ).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"')); }); }); @@ -199,20 +164,6 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse).toEqual(getExceptionListSchemaMock()); }); - test('it returns error and does not make request if request payload fails decode', async () => { - const payload = getUpdateExceptionListSchemaMock(); - // @ts-expect-error - delete payload.description; - - await expect( - updateExceptionList({ - http: httpMock, - list: payload, - signal: abortCtrl.signal, - }) - ).rejects.toEqual('Invalid value "undefined" supplied to "description"'); - }); - test('it returns error if response payload fails decode', async () => { const payload = getUpdateExceptionListSchemaMock(); const badPayload = getExceptionListSchemaMock(); @@ -226,7 +177,7 @@ describe('Exceptions Lists API', () => { list: payload, signal: abortCtrl.signal, }) - ).rejects.toEqual('Invalid value "undefined" supplied to "id"'); + ).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"')); }); }); @@ -261,20 +212,6 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock()); }); - test('it returns error and does not make request if request payload fails decode', async () => { - const payload = getUpdateExceptionListItemSchemaMock(); - // @ts-expect-error - delete payload.description; - - await expect( - updateExceptionListItem({ - http: httpMock, - listItem: payload, - signal: abortCtrl.signal, - }) - ).rejects.toEqual('Invalid value "undefined" supplied to "description"'); - }); - test('it returns error if response payload fails decode', async () => { const payload = getUpdateExceptionListItemSchemaMock(); const badPayload = getExceptionListItemSchemaMock(); @@ -288,7 +225,7 @@ describe('Exceptions Lists API', () => { listItem: payload, signal: abortCtrl.signal, }) - ).rejects.toEqual('Invalid value "undefined" supplied to "id"'); + ).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"')); }); }); @@ -336,22 +273,6 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse.data).toEqual([getExceptionListSchemaMock()]); }); - test('it returns error and does not make request if request payload fails decode', async () => { - const payload = ({ - filters: 'exception-list.attributes.name: Sample Endpoint', - http: httpMock, - namespaceTypes: 'notANamespaceType', - pagination: { - page: 1, - perPage: 20, - }, - signal: abortCtrl.signal, - } as unknown) as ApiCallFetchExceptionListsProps & { namespaceTypes: string[] }; - await expect(fetchExceptionLists(payload)).rejects.toEqual( - 'Invalid value "notANamespaceType" supplied to "namespace_type"' - ); - }); - test('it returns error if response payload fails decode', async () => { const badPayload = getExceptionListSchemaMock(); // @ts-expect-error @@ -369,7 +290,7 @@ describe('Exceptions Lists API', () => { }, signal: abortCtrl.signal, }) - ).rejects.toEqual('Invalid value "undefined" supplied to "data,id"'); + ).rejects.toEqual(new Error('Invalid value "undefined" supplied to "data,id"')); }); }); @@ -405,18 +326,6 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse).toEqual(getExceptionListSchemaMock()); }); - test('it returns error and does not make request if request payload fails decode', async () => { - const payload = ({ - http: httpMock, - id: 1, - namespaceType: 'single', - signal: abortCtrl.signal, - } as unknown) as ApiCallByIdProps & { id: number }; - await expect(fetchExceptionListById(payload)).rejects.toEqual( - 'Invalid value "1" supplied to "id"' - ); - }); - test('it returns error if response payload fails decode', async () => { const badPayload = getExceptionListSchemaMock(); // @ts-expect-error @@ -430,7 +339,7 @@ describe('Exceptions Lists API', () => { namespaceType: 'single', signal: abortCtrl.signal, }) - ).rejects.toEqual('Invalid value "undefined" supplied to "id"'); + ).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"')); }); }); @@ -614,23 +523,6 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse).toEqual(getFoundExceptionListItemSchemaMock()); }); - test('it returns error and does not make request if request payload fails decode', async () => { - const payload = ({ - filterOptions: [], - http: httpMock, - listIds: ['myList'], - namespaceTypes: ['not a namespace type'], - pagination: { - page: 1, - perPage: 20, - }, - signal: abortCtrl.signal, - } as unknown) as ApiCallByListIdProps & { listId: number }; - await expect(fetchExceptionListsItemsByListIds(payload)).rejects.toEqual( - 'Invalid value "not a namespace type" supplied to "namespace_type"' - ); - }); - test('it returns error if response payload fails decode', async () => { const badPayload = getExceptionListItemSchemaMock(); // @ts-expect-error @@ -650,7 +542,9 @@ describe('Exceptions Lists API', () => { signal: abortCtrl.signal, }) ).rejects.toEqual( - 'Invalid value "undefined" supplied to "data",Invalid value "undefined" supplied to "page",Invalid value "undefined" supplied to "per_page",Invalid value "undefined" supplied to "total"' + new Error( + 'Invalid value "undefined" supplied to "data",Invalid value "undefined" supplied to "page",Invalid value "undefined" supplied to "per_page",Invalid value "undefined" supplied to "total"' + ) ); }); }); @@ -687,18 +581,6 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock()); }); - test('it returns error and does not make request if request payload fails decode', async () => { - const payload = ({ - http: httpMock, - id: '1', - namespaceType: 'not a namespace type', - signal: abortCtrl.signal, - } as unknown) as ApiCallByIdProps & { namespaceType: string }; - await expect(fetchExceptionListItemById(payload)).rejects.toEqual( - 'Invalid value "not a namespace type" supplied to "namespace_type"' - ); - }); - test('it returns error if response payload fails decode', async () => { const badPayload = getExceptionListItemSchemaMock(); // @ts-expect-error @@ -712,7 +594,7 @@ describe('Exceptions Lists API', () => { namespaceType: 'single', signal: abortCtrl.signal, }) - ).rejects.toEqual('Invalid value "undefined" supplied to "id"'); + ).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"')); }); }); @@ -748,18 +630,6 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse).toEqual(getExceptionListSchemaMock()); }); - test('it returns error and does not make request if request payload fails decode', async () => { - const payload = ({ - http: httpMock, - id: 1, - namespaceType: 'single', - signal: abortCtrl.signal, - } as unknown) as ApiCallByIdProps & { id: number }; - await expect(deleteExceptionListById(payload)).rejects.toEqual( - 'Invalid value "1" supplied to "id"' - ); - }); - test('it returns error if response payload fails decode', async () => { const badPayload = getExceptionListSchemaMock(); // @ts-expect-error @@ -773,7 +643,7 @@ describe('Exceptions Lists API', () => { namespaceType: 'single', signal: abortCtrl.signal, }) - ).rejects.toEqual('Invalid value "undefined" supplied to "id"'); + ).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"')); }); }); @@ -809,18 +679,6 @@ describe('Exceptions Lists API', () => { expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock()); }); - test('it returns error and does not make request if request payload fails decode', async () => { - const payload = ({ - http: httpMock, - id: 1, - namespaceType: 'single', - signal: abortCtrl.signal, - } as unknown) as ApiCallByIdProps & { id: number }; - await expect(deleteExceptionListItemById(payload)).rejects.toEqual( - 'Invalid value "1" supplied to "id"' - ); - }); - test('it returns error if response payload fails decode', async () => { const badPayload = getExceptionListItemSchemaMock(); // @ts-expect-error @@ -834,7 +692,7 @@ describe('Exceptions Lists API', () => { namespaceType: 'single', signal: abortCtrl.signal, }) - ).rejects.toEqual('Invalid value "undefined" supplied to "id"'); + ).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"')); }); }); diff --git a/x-pack/plugins/lists/public/exceptions/api.ts b/x-pack/plugins/lists/public/exceptions/api.ts index f7032c22cb6c2..8fcd1af524f6d 100644 --- a/x-pack/plugins/lists/public/exceptions/api.ts +++ b/x-pack/plugins/lists/public/exceptions/api.ts @@ -3,6 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { chain, fromEither, tryCatch } from 'fp-ts/lib/TaskEither'; +import { flow } from 'fp-ts/lib/function'; + +import { validateEither } from '../../common/shared_imports'; +import { toError, toPromise } from '../common/fp_utils'; import { ENDPOINT_LIST_URL, EXCEPTION_LIST_ITEM_URL, @@ -17,22 +22,11 @@ import { FoundExceptionListItemSchema, FoundExceptionListSchema, createEndpointListSchema, - createExceptionListItemSchema, - createExceptionListSchema, - deleteExceptionListItemSchema, - deleteExceptionListSchema, exceptionListItemSchema, exceptionListSchema, - findExceptionListItemSchema, - findExceptionListSchema, foundExceptionListItemSchema, foundExceptionListSchema, - readExceptionListItemSchema, - readExceptionListSchema, - updateExceptionListItemSchema, - updateExceptionListSchema, } from '../../common/schemas'; -import { validate } from '../../common/shared_imports'; import { AddEndpointExceptionListProps, @@ -56,35 +50,38 @@ import { * @throws An error if response is not OK * */ -export const addExceptionList = async ({ +const addExceptionList = async ({ http, list, signal, -}: AddExceptionListProps): Promise => { - const [validatedRequest, errorsRequest] = validate(list, createExceptionListSchema); - - if (validatedRequest != null) { - try { - const response = await http.fetch(EXCEPTION_LIST_URL, { - body: JSON.stringify(list), - method: 'POST', - signal, - }); - - const [validatedResponse, errorsResponse] = validate(response, exceptionListSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } - } else { - return Promise.reject(errorsRequest); - } -}; +}: AddExceptionListProps): Promise => + http.fetch(EXCEPTION_LIST_URL, { + body: JSON.stringify(list), + method: 'POST', + signal, + }); + +const addExceptionListWithValidation = async ({ + http, + list, + signal, +}: AddExceptionListProps): Promise => + flow( + () => + tryCatch( + () => + addExceptionList({ + http, + list, + signal, + }), + toError + ), + chain((response) => fromEither(validateEither(exceptionListSchema, response))), + flow(toPromise) + )(); + +export { addExceptionListWithValidation as addExceptionList }; /** * Add new ExceptionListItem @@ -96,35 +93,38 @@ export const addExceptionList = async ({ * @throws An error if response is not OK * */ -export const addExceptionListItem = async ({ +const addExceptionListItem = async ({ http, listItem, signal, -}: AddExceptionListItemProps): Promise => { - const [validatedRequest, errorsRequest] = validate(listItem, createExceptionListItemSchema); - - if (validatedRequest != null) { - try { - const response = await http.fetch(EXCEPTION_LIST_ITEM_URL, { - body: JSON.stringify(listItem), - method: 'POST', - signal, - }); - - const [validatedResponse, errorsResponse] = validate(response, exceptionListItemSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } - } else { - return Promise.reject(errorsRequest); - } -}; +}: AddExceptionListItemProps): Promise => + http.fetch(EXCEPTION_LIST_ITEM_URL, { + body: JSON.stringify(listItem), + method: 'POST', + signal, + }); + +const addExceptionListItemWithValidation = async ({ + http, + listItem, + signal, +}: AddExceptionListItemProps): Promise => + flow( + () => + tryCatch( + () => + addExceptionListItem({ + http, + listItem, + signal, + }), + toError + ), + chain((response) => fromEither(validateEither(exceptionListItemSchema, response))), + flow(toPromise) + )(); + +export { addExceptionListItemWithValidation as addExceptionListItem }; /** * Update existing ExceptionList @@ -136,35 +136,38 @@ export const addExceptionListItem = async ({ * @throws An error if response is not OK * */ -export const updateExceptionList = async ({ +const updateExceptionList = async ({ http, list, signal, -}: UpdateExceptionListProps): Promise => { - const [validatedRequest, errorsRequest] = validate(list, updateExceptionListSchema); - - if (validatedRequest != null) { - try { - const response = await http.fetch(EXCEPTION_LIST_URL, { - body: JSON.stringify(list), - method: 'PUT', - signal, - }); - - const [validatedResponse, errorsResponse] = validate(response, exceptionListSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } - } else { - return Promise.reject(errorsRequest); - } -}; +}: UpdateExceptionListProps): Promise => + http.fetch(EXCEPTION_LIST_URL, { + body: JSON.stringify(list), + method: 'PUT', + signal, + }); + +const updateExceptionListWithValidation = async ({ + http, + list, + signal, +}: UpdateExceptionListProps): Promise => + flow( + () => + tryCatch( + () => + updateExceptionList({ + http, + list, + signal, + }), + toError + ), + chain((response) => fromEither(validateEither(exceptionListSchema, response))), + flow(toPromise) + )(); + +export { updateExceptionListWithValidation as updateExceptionList }; /** * Update existing ExceptionListItem @@ -176,35 +179,38 @@ export const updateExceptionList = async ({ * @throws An error if response is not OK * */ -export const updateExceptionListItem = async ({ +const updateExceptionListItem = async ({ http, listItem, signal, -}: UpdateExceptionListItemProps): Promise => { - const [validatedRequest, errorsRequest] = validate(listItem, updateExceptionListItemSchema); - - if (validatedRequest != null) { - try { - const response = await http.fetch(EXCEPTION_LIST_ITEM_URL, { - body: JSON.stringify(listItem), - method: 'PUT', - signal, - }); - - const [validatedResponse, errorsResponse] = validate(response, exceptionListItemSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } - } else { - return Promise.reject(errorsRequest); - } -}; +}: UpdateExceptionListItemProps): Promise => + http.fetch(EXCEPTION_LIST_ITEM_URL, { + body: JSON.stringify(listItem), + method: 'PUT', + signal, + }); + +const updateExceptionListItemWithValidation = async ({ + http, + listItem, + signal, +}: UpdateExceptionListItemProps): Promise => + flow( + () => + tryCatch( + () => + updateExceptionListItem({ + http, + listItem, + signal, + }), + toError + ), + chain((response) => fromEither(validateEither(exceptionListItemSchema, response))), + flow(toPromise) + )(); + +export { updateExceptionListItemWithValidation as updateExceptionListItem }; /** * Fetch all ExceptionLists (optionally by namespaceType) @@ -217,7 +223,7 @@ export const updateExceptionListItem = async ({ * * @throws An error if request params or response is not OK */ -export const fetchExceptionLists = async ({ +const fetchExceptionLists = async ({ http, filters, namespaceTypes, @@ -233,31 +239,39 @@ export const fetchExceptionLists = async ({ sort_order: 'desc', }; - const [validatedRequest, errorsRequest] = validate(query, findExceptionListSchema); - - if (validatedRequest != null) { - try { - const response = await http.fetch(`${EXCEPTION_LIST_URL}/_find`, { - method: 'GET', - query, - signal, - }); - - const [validatedResponse, errorsResponse] = validate(response, foundExceptionListSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } - } else { - return Promise.reject(errorsRequest); - } + return http.fetch(`${EXCEPTION_LIST_URL}/_find`, { + method: 'GET', + query, + signal, + }); }; +const fetchExceptionListsWithValidation = async ({ + filters, + http, + namespaceTypes, + pagination, + signal, +}: ApiCallFetchExceptionListsProps): Promise => + flow( + () => + tryCatch( + () => + fetchExceptionLists({ + filters, + http, + namespaceTypes, + pagination, + signal, + }), + toError + ), + chain((response) => fromEither(validateEither(foundExceptionListSchema, response))), + flow(toPromise) + )(); + +export { fetchExceptionListsWithValidation as fetchExceptionLists }; + /** * Fetch an ExceptionList by providing a ExceptionList ID * @@ -268,39 +282,41 @@ export const fetchExceptionLists = async ({ * * @throws An error if response is not OK */ -export const fetchExceptionListById = async ({ +const fetchExceptionListById = async ({ http, id, namespaceType, signal, -}: ApiCallByIdProps): Promise => { - const [validatedRequest, errorsRequest] = validate( - { id, namespace_type: namespaceType }, - readExceptionListSchema - ); - - if (validatedRequest != null) { - try { - const response = await http.fetch(EXCEPTION_LIST_URL, { - method: 'GET', - query: { id, namespace_type: namespaceType }, - signal, - }); - - const [validatedResponse, errorsResponse] = validate(response, exceptionListSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } - } else { - return Promise.reject(errorsRequest); - } -}; +}: ApiCallByIdProps): Promise => + http.fetch(EXCEPTION_LIST_URL, { + method: 'GET', + query: { id, namespace_type: namespaceType }, + signal, + }); + +const fetchExceptionListByIdWithValidation = async ({ + http, + id, + namespaceType, + signal, +}: ApiCallByIdProps): Promise => + flow( + () => + tryCatch( + () => + fetchExceptionListById({ + http, + id, + namespaceType, + signal, + }), + toError + ), + chain((response) => fromEither(validateEither(exceptionListSchema, response))), + flow(toPromise) + )(); + +export { fetchExceptionListByIdWithValidation as fetchExceptionListById }; /** * Fetch an ExceptionList's ExceptionItems by providing a ExceptionList list_id @@ -314,7 +330,7 @@ export const fetchExceptionListById = async ({ * * @throws An error if response is not OK */ -export const fetchExceptionListsItemsByListIds = async ({ +const fetchExceptionListsItemsByListIds = async ({ http, listIds, namespaceTypes, @@ -349,34 +365,42 @@ export const fetchExceptionListsItemsByListIds = async ({ sort_order: 'desc', ...(filters.trim() !== '' ? { filter: filters } : {}), }; - const [validatedRequest, errorsRequest] = validate(query, findExceptionListItemSchema); - - if (validatedRequest != null) { - try { - const response = await http.fetch( - `${EXCEPTION_LIST_ITEM_URL}/_find`, - { - method: 'GET', - query, - signal, - } - ); - - const [validatedResponse, errorsResponse] = validate(response, foundExceptionListItemSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } - } else { - return Promise.reject(errorsRequest); - } + + return http.fetch(`${EXCEPTION_LIST_ITEM_URL}/_find`, { + method: 'GET', + query, + signal, + }); }; +const fetchExceptionListsItemsByListIdsWithValidation = async ({ + filterOptions, + http, + listIds, + namespaceTypes, + pagination, + signal, +}: ApiCallByListIdProps): Promise => + flow( + () => + tryCatch( + () => + fetchExceptionListsItemsByListIds({ + filterOptions, + http, + listIds, + namespaceTypes, + pagination, + signal, + }), + toError + ), + chain((response) => fromEither(validateEither(foundExceptionListItemSchema, response))), + flow(toPromise) + )(); + +export { fetchExceptionListsItemsByListIdsWithValidation as fetchExceptionListsItemsByListIds }; + /** * Fetch an ExceptionListItem by providing a ExceptionListItem ID * @@ -387,38 +411,31 @@ export const fetchExceptionListsItemsByListIds = async ({ * * @throws An error if response is not OK */ -export const fetchExceptionListItemById = async ({ +const fetchExceptionListItemById = async ({ http, id, namespaceType, signal, -}: ApiCallByIdProps): Promise => { - const [validatedRequest, errorsRequest] = validate( - { id, namespace_type: namespaceType }, - readExceptionListItemSchema - ); - - if (validatedRequest != null) { - try { - const response = await http.fetch(EXCEPTION_LIST_ITEM_URL, { - method: 'GET', - query: { id, namespace_type: namespaceType }, - signal, - }); - const [validatedResponse, errorsResponse] = validate(response, exceptionListItemSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } - } else { - return Promise.reject(errorsRequest); - } -}; +}: ApiCallByIdProps): Promise => + http.fetch(EXCEPTION_LIST_ITEM_URL, { + method: 'GET', + query: { id, namespace_type: namespaceType }, + signal, + }); + +const fetchExceptionListItemByIdWithValidation = async ({ + http, + id, + namespaceType, + signal, +}: ApiCallByIdProps): Promise => + flow( + () => tryCatch(() => fetchExceptionListItemById({ http, id, namespaceType, signal }), toError), + chain((response) => fromEither(validateEither(exceptionListItemSchema, response))), + flow(toPromise) + )(); + +export { fetchExceptionListItemByIdWithValidation as fetchExceptionListItemById }; /** * Delete an ExceptionList by providing a ExceptionList ID @@ -430,39 +447,31 @@ export const fetchExceptionListItemById = async ({ * * @throws An error if response is not OK */ -export const deleteExceptionListById = async ({ +const deleteExceptionListById = async ({ http, id, namespaceType, signal, -}: ApiCallByIdProps): Promise => { - const [validatedRequest, errorsRequest] = validate( - { id, namespace_type: namespaceType }, - deleteExceptionListSchema - ); - - if (validatedRequest != null) { - try { - const response = await http.fetch(EXCEPTION_LIST_URL, { - method: 'DELETE', - query: { id, namespace_type: namespaceType }, - signal, - }); - - const [validatedResponse, errorsResponse] = validate(response, exceptionListSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } - } else { - return Promise.reject(errorsRequest); - } -}; +}: ApiCallByIdProps): Promise => + http.fetch(EXCEPTION_LIST_URL, { + method: 'DELETE', + query: { id, namespace_type: namespaceType }, + signal, + }); + +const deleteExceptionListByIdWithValidation = async ({ + http, + id, + namespaceType, + signal, +}: ApiCallByIdProps): Promise => + flow( + () => tryCatch(() => deleteExceptionListById({ http, id, namespaceType, signal }), toError), + chain((response) => fromEither(validateEither(exceptionListSchema, response))), + flow(toPromise) + )(); + +export { deleteExceptionListByIdWithValidation as deleteExceptionListById }; /** * Delete an ExceptionListItem by providing a ExceptionListItem ID @@ -474,39 +483,31 @@ export const deleteExceptionListById = async ({ * * @throws An error if response is not OK */ -export const deleteExceptionListItemById = async ({ +const deleteExceptionListItemById = async ({ http, id, namespaceType, signal, -}: ApiCallByIdProps): Promise => { - const [validatedRequest, errorsRequest] = validate( - { id, namespace_type: namespaceType }, - deleteExceptionListItemSchema - ); - - if (validatedRequest != null) { - try { - const response = await http.fetch(EXCEPTION_LIST_ITEM_URL, { - method: 'DELETE', - query: { id, namespace_type: namespaceType }, - signal, - }); - - const [validatedResponse, errorsResponse] = validate(response, exceptionListItemSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } - } else { - return Promise.reject(errorsRequest); - } -}; +}: ApiCallByIdProps): Promise => + http.fetch(EXCEPTION_LIST_ITEM_URL, { + method: 'DELETE', + query: { id, namespace_type: namespaceType }, + signal, + }); + +const deleteExceptionListItemByIdWithValidation = async ({ + http, + id, + namespaceType, + signal, +}: ApiCallByIdProps): Promise => + flow( + () => tryCatch(() => deleteExceptionListItemById({ http, id, namespaceType, signal }), toError), + chain((response) => fromEither(validateEither(exceptionListItemSchema, response))), + flow(toPromise) + )(); + +export { deleteExceptionListItemByIdWithValidation as deleteExceptionListItemById }; /** * Add new Endpoint ExceptionList @@ -517,27 +518,26 @@ export const deleteExceptionListItemById = async ({ * @throws An error if response is not OK * */ -export const addEndpointExceptionList = async ({ +const addEndpointExceptionList = async ({ http, signal, -}: AddEndpointExceptionListProps): Promise => { - try { - const response = await http.fetch(ENDPOINT_LIST_URL, { - method: 'POST', - signal, - }); - - const [validatedResponse, errorsResponse] = validate(response, createEndpointListSchema); - - if (errorsResponse != null || validatedResponse == null) { - return Promise.reject(errorsResponse); - } else { - return Promise.resolve(validatedResponse); - } - } catch (error) { - return Promise.reject(error); - } -}; +}: AddEndpointExceptionListProps): Promise => + http.fetch(ENDPOINT_LIST_URL, { + method: 'POST', + signal, + }); + +const addEndpointExceptionListWithValidation = async ({ + http, + signal, +}: AddEndpointExceptionListProps): Promise => + flow( + () => tryCatch(() => addEndpointExceptionList({ http, signal }), toError), + chain((response) => fromEither(validateEither(createEndpointListSchema, response))), + flow(toPromise) + )(); + +export { addEndpointExceptionListWithValidation as addEndpointExceptionList }; /** * Fetch an ExceptionList by providing a ExceptionList ID From deae756756612402665e9f4f3ba14a62104023fb Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 23 Dec 2020 21:13:20 -0500 Subject: [PATCH 48/72] [Security Solution] Fix flow/draggable in details event (#86834) * fix details event * fix types + add unit test * review with angela * fix lint error Co-authored-by: Angela Chuang --- .../common/types/timeline/index.ts | 19 ++- .../__snapshots__/event_details.test.tsx.snap | 2 + .../event_details/event_details.test.tsx | 4 +- .../event_details/event_details.tsx | 6 +- .../event_fields_browser.test.tsx | 11 ++ .../event_details/event_fields_browser.tsx | 7 +- .../events_viewer/event_details_flyout.tsx | 3 +- .../events_viewer/events_viewer.test.tsx | 157 +++++++++++++----- .../events_viewer/events_viewer.tsx | 39 +---- .../common/components/events_viewer/index.tsx | 2 +- .../common/components/events_viewer/mock.ts | 88 ++++++++++ .../navigation/breadcrumbs/index.test.ts | 2 +- .../components/navigation/index.test.tsx | 2 +- .../navigation/tab_navigation/index.test.tsx | 2 +- .../common/components/url_state/helpers.ts | 4 +- .../components/url_state/test_dependencies.ts | 2 +- .../public/common/mock/global_state.ts | 3 +- .../public/common/mock/timeline_results.ts | 9 +- .../components/alerts_table/actions.test.tsx | 8 +- .../flyout/bottom_bar/index.test.tsx | 2 +- .../components/flyout/bottom_bar/index.tsx | 2 +- .../components/flyout/header/index.tsx | 3 +- .../timelines/components/flyout/index.tsx | 3 +- .../timelines/components/flyout/selectors.ts | 3 +- .../components/open_timeline/helpers.test.ts | 8 +- .../components/open_timeline/helpers.ts | 7 +- .../open_timeline/note_previews/index.tsx | 2 + .../body/data_driven_columns/index.tsx | 3 +- .../body/events/event_column_view.test.tsx | 3 +- .../body/events/event_column_view.tsx | 4 +- .../components/timeline/body/events/index.tsx | 3 +- .../timeline/body/events/stateful_event.tsx | 14 +- .../components/timeline/body/helpers.tsx | 3 +- .../components/timeline/body/index.test.tsx | 76 ++++++++- .../components/timeline/body/index.tsx | 4 +- .../components/timeline/event_details.tsx | 6 +- .../timeline/expandable_event/index.tsx | 8 +- .../timelines/components/timeline/index.tsx | 6 +- .../timeline/notes_tab_content/index.tsx | 40 +++-- .../timeline/notes_tab_content/selectors.ts | 20 +++ .../timeline/pinned_tab_content/index.tsx | 8 +- .../timeline/query_tab_content/index.test.tsx | 3 +- .../timeline/query_tab_content/index.tsx | 34 +--- .../timeline/tabs_content/index.tsx | 39 ++++- .../timeline/tabs_content/selectors.ts | 10 +- .../containers/active_timeline_context.ts | 8 +- .../timelines/containers/details/index.tsx | 11 +- .../timelines/store/timeline/actions.ts | 9 +- .../timelines/store/timeline/defaults.ts | 4 +- .../timelines/store/timeline/epic.test.ts | 4 +- .../timeline/epic_local_storage.test.tsx | 3 +- .../public/timelines/store/timeline/model.ts | 9 +- .../timelines/store/timeline/reducer.test.ts | 4 +- .../timelines/store/timeline/reducer.ts | 26 +-- 54 files changed, 535 insertions(+), 227 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/selectors.ts diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index aa114ff074898..26d13b13f40cb 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -408,12 +408,23 @@ export type ImportTimelineResultSchema = runtimeTypes.TypeOf; -export type TimelineExpandedEvent = TimelineExpandedEventType | EmptyObject; +export type TimelineExpandedEventType = + | { + eventId: string; + indexName: string; + } + | EmptyObject; + +export type TimelineExpandedEvent = { + [tab in TimelineTabs]?: TimelineExpandedEventType; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap index 973d067d9e379..e9b11d9bcdf71 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap @@ -577,6 +577,7 @@ exports[`EventDetails rendering should match snapshot 1`] = ` } eventId="Y-6TfmcB0WOhS6qyMv3s" timelineId="test" + timelineTabType="query" /> , "id": "table-view", @@ -1157,6 +1158,7 @@ exports[`EventDetails rendering should match snapshot 1`] = ` } eventId="Y-6TfmcB0WOhS6qyMv3s" timelineId="test" + timelineTabType="query" /> , "id": "table-view", diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index 4659006050781..9ab286b120dd3 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { waitFor } from '@testing-library/dom'; import { ReactWrapper, shallow } from 'enzyme'; import React from 'react'; @@ -16,7 +17,7 @@ import { mockBrowserFields } from '../../containers/source/mock'; import { useMountAppended } from '../../utils/use_mount_appended'; import { mockAlertDetailsData } from './__mocks__'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -import { waitFor } from '@testing-library/dom'; +import { TimelineTabs } from '../../../../common/types/timeline'; jest.mock('../link_to'); describe('EventDetails', () => { @@ -27,6 +28,7 @@ describe('EventDetails', () => { id: mockDetailItemDataId, isAlert: false, onViewSelected: jest.fn(), + timelineTabType: TimelineTabs.query, timelineId: 'test', view: EventsViewType.summaryView, }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 291893fe682b4..123a3fa7b610b 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -14,6 +14,7 @@ import { EventFieldsBrowser } from './event_fields_browser'; import { JsonView } from './json_view'; import * as i18n from './translations'; import { SummaryView } from './summary_view'; +import { TimelineTabs } from '../../../../common/types/timeline'; export type View = EventsViewType.tableView | EventsViewType.jsonView | EventsViewType.summaryView; export enum EventsViewType { @@ -29,6 +30,7 @@ interface Props { isAlert: boolean; view: EventsViewType; onViewSelected: (selected: EventsViewType) => void; + timelineTabType: TimelineTabs | 'flyout'; timelineId: string; } @@ -52,6 +54,7 @@ const EventDetailsComponent: React.FC = ({ id, view, onViewSelected, + timelineTabType, timelineId, isAlert, }) => { @@ -91,6 +94,7 @@ const EventDetailsComponent: React.FC = ({ data={data} eventId={id} timelineId={timelineId} + timelineTabType={timelineTabType} /> ), @@ -106,7 +110,7 @@ const EventDetailsComponent: React.FC = ({ ), }, ], - [alerts, browserFields, data, id, isAlert, timelineId] + [alerts, browserFields, data, id, isAlert, timelineId, timelineTabType] ); const selectedTab = useMemo(() => tabs.find((t) => t.id === view) ?? tabs[0], [tabs, view]); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx index cd50eb7880e56..0fc29e7193d4d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx @@ -13,6 +13,7 @@ import { timelineActions } from '../../../timelines/store/timeline'; import { EventFieldsBrowser } from './event_fields_browser'; import { mockBrowserFields } from '../../containers/source/mock'; import { useMountAppended } from '../../utils/use_mount_appended'; +import { TimelineTabs } from '../../../../common/types/timeline'; jest.mock('@elastic/eui', () => { const original = jest.requireActual('@elastic/eui'); @@ -48,6 +49,7 @@ describe('EventFieldsBrowser', () => { data={mockDetailItemData} eventId={mockDetailItemDataId} timelineId="test" + timelineTabType={TimelineTabs.query} /> ); @@ -66,6 +68,7 @@ describe('EventFieldsBrowser', () => { data={mockDetailItemData} eventId={mockDetailItemDataId} timelineId="test" + timelineTabType={TimelineTabs.query} /> ); @@ -89,6 +92,7 @@ describe('EventFieldsBrowser', () => { data={mockDetailItemData} eventId={eventId} timelineId="test" + timelineTabType={TimelineTabs.query} /> ); @@ -108,6 +112,7 @@ describe('EventFieldsBrowser', () => { data={mockDetailItemData} eventId={eventId} timelineId="test" + timelineTabType={TimelineTabs.query} /> ); @@ -127,6 +132,7 @@ describe('EventFieldsBrowser', () => { data={mockDetailItemData} eventId={eventId} timelineId="test" + timelineTabType={TimelineTabs.query} /> ); @@ -158,6 +164,7 @@ describe('EventFieldsBrowser', () => { data={mockDetailItemData} eventId={mockDetailItemDataId} timelineId="test" + timelineTabType={TimelineTabs.query} /> ); @@ -182,6 +189,7 @@ describe('EventFieldsBrowser', () => { data={mockDetailItemData} eventId={mockDetailItemDataId} timelineId="test" + timelineTabType={TimelineTabs.query} /> ); @@ -196,6 +204,7 @@ describe('EventFieldsBrowser', () => { data={mockDetailItemData} eventId={mockDetailItemDataId} timelineId="test" + timelineTabType={TimelineTabs.query} /> ); @@ -220,6 +229,7 @@ describe('EventFieldsBrowser', () => { data={mockDetailItemData} eventId={mockDetailItemDataId} timelineId="test" + timelineTabType={TimelineTabs.query} /> ); @@ -238,6 +248,7 @@ describe('EventFieldsBrowser', () => { data={mockDetailItemData} eventId={mockDetailItemDataId} timelineId="test" + timelineTabType={TimelineTabs.query} /> ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx index cd1579b299093..9733fafbe1c4d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx @@ -29,12 +29,14 @@ import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { getColumns } from './columns'; import { EVENT_FIELDS_TABLE_CLASS_NAME, onEventDetailsTabKeyPressed, search } from './helpers'; import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { TimelineTabs } from '../../../../common/types/timeline'; interface Props { browserFields: BrowserFields; data: TimelineEventsDetailsItem[]; eventId: string; timelineId: string; + timelineTabType: TimelineTabs | 'flyout'; } const TableWrapper = styled.div` @@ -87,7 +89,7 @@ const getAriaRowindex = (timelineEventsDetailsItem: TimelineEventsDetailsItem) = /** Renders a table view or JSON view of the `ECS` `data` */ export const EventFieldsBrowser = React.memo( - ({ browserFields, data, eventId, timelineId }) => { + ({ browserFields, data, eventId, timelineTabType, timelineId }) => { const containerElement = useRef(null); const dispatch = useDispatch(); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); @@ -156,7 +158,7 @@ export const EventFieldsBrowser = React.memo( columnHeaders, eventId, onUpdateColumns, - contextId: `event-fields-browser-for-${timelineId}`, + contextId: `event-fields-browser-for-${timelineId}-${timelineTabType}`, timelineId, toggleColumn, getLinkValue, @@ -167,6 +169,7 @@ export const EventFieldsBrowser = React.memo( eventId, onUpdateColumns, timelineId, + timelineTabType, toggleColumn, getLinkValue, ] diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx index 48bdebbc0aa4f..9c09f2e696104 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx @@ -39,7 +39,7 @@ const EventDetailsFlyoutComponent: React.FC = ({ const dispatch = useDispatch(); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const expandedEvent = useDeepEqualSelector( - (state) => (getTimeline(state, timelineId) ?? timelineDefaults)?.expandedEvent ?? {} + (state) => (getTimeline(state, timelineId) ?? timelineDefaults)?.expandedEvent?.query ?? {} ); const handleClearSelection = useCallback(() => { @@ -75,6 +75,7 @@ const EventDetailsFlyoutComponent: React.FC = ({ isAlert={isAlert} loading={loading} timelineId={timelineId} + timelineTabType="flyout" /> diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index 423b3566e4eb5..6250345579cff 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -5,12 +5,13 @@ */ import React from 'react'; +import { waitFor, act } from '@testing-library/react'; import useResizeObserver from 'use-resize-observer/polyfilled'; import '../../mock/match_media'; import { mockIndexNames, mockIndexPattern, TestProviders } from '../../mock'; -import { mockEventViewerResponse } from './mock'; +import { mockEventViewerResponse, mockEventViewerResponseWithEvents } from './mock'; import { StatefulEventsViewer } from '.'; import { EventsViewer } from './events_viewer'; import { defaultHeaders } from './default_headers'; @@ -30,6 +31,15 @@ jest.mock('../../../timelines/components/graph_overlay', () => ({ GraphOverlay: jest.fn(() =>
    ), })); +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + return { + ...original, + useDispatch: () => mockDispatch, + }; +}); + jest.mock('@elastic/eui', () => { const original = jest.requireActual('@elastic/eui'); return { @@ -50,6 +60,9 @@ const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock; jest.mock('use-resize-observer/polyfilled'); mockUseResizeObserver.mockImplementation(() => ({})); +const mockUseTimelineEvents: jest.Mock = useTimelineEvents as jest.Mock; +jest.mock('../../../timelines/containers'); + const from = '2019-08-26T22:10:56.791Z'; const to = '2019-08-27T22:10:56.794Z'; @@ -108,14 +121,51 @@ describe('EventsViewer', () => { start: from, scopeId: SourcererScopeName.timeline, }; - beforeEach(() => { - (useTimelineEvents as jest.Mock).mockReturnValue([false, mockEventViewerResponse]); + mockUseTimelineEvents.mockReset(); }); beforeAll(() => { mockUseSourcererScope.mockImplementation(() => defaultMocks); }); + + describe('event details', () => { + beforeEach(() => { + mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponseWithEvents]); + }); + + test('call the right reduce action to show event details', async () => { + const wrapper = mount( + + + + ); + + await act(async () => { + wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click'); + }); + + await waitFor(() => { + expect(mockDispatch).toBeCalledTimes(2); + expect(mockDispatch.mock.calls[1][0]).toEqual({ + payload: { + event: { + eventId: 'yb8TkHYBRgU82_bJu_rY', + indexName: 'auditbeat-7.10.1-2020.12.18-000001', + }, + tabType: 'query', + timelineId: 'test-stateful-events-viewer', + }, + type: 'x-pack/security_solution/local/timeline/TOGGLE_EXPANDED_EVENT', + }); + }); + }); + }); + describe('rendering', () => { + beforeEach(() => { + mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]); + }); + test('it renders the "Showing..." subtitle with the expected event count', () => { const wrapper = mount( @@ -160,57 +210,66 @@ describe('EventsViewer', () => { ); }); }); - describe('loading', () => { - beforeAll(() => { - mockUseSourcererScope.mockImplementation(() => ({ ...defaultMocks, loading: true })); - }); - test('it does NOT render fetch index pattern is loading', () => { - const wrapper = mount( - - - - ); + }); - expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe( - false - ); - }); + describe('loading', () => { + beforeAll(() => { + mockUseSourcererScope.mockImplementation(() => ({ ...defaultMocks, loading: true })); + }); + beforeEach(() => { + mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]); + }); - test('it does NOT render when start is empty', () => { - testProps = { - ...testProps, - start: '', - }; - const wrapper = mount( - - - - ); + test('it does NOT render fetch index pattern is loading', () => { + const wrapper = mount( + + + + ); - expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe( - false - ); - }); + expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe( + false + ); + }); - test('it does NOT render when end is empty', () => { - testProps = { - ...testProps, - end: '', - }; - const wrapper = mount( - - - - ); + test('it does NOT render when start is empty', () => { + testProps = { + ...testProps, + start: '', + }; + const wrapper = mount( + + + + ); - expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe( - false - ); - }); + expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe( + false + ); + }); + + test('it does NOT render when end is empty', () => { + testProps = { + ...testProps, + end: '', + }; + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe( + false + ); }); }); describe('headerFilterGroup', () => { + beforeEach(() => { + mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]); + }); + test('it renders the provided headerFilterGroup', () => { const wrapper = mount( @@ -284,6 +343,10 @@ describe('EventsViewer', () => { }); describe('utilityBar', () => { + beforeEach(() => { + mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]); + }); + test('it renders the provided utilityBar when Resolver is NOT showing, because graphEventId is undefined', () => { const wrapper = mount( @@ -313,6 +376,10 @@ describe('EventsViewer', () => { }); describe('header inspect button', () => { + beforeEach(() => { + mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]); + }); + test('it renders the inspect button when Resolver is NOT showing, because graphEventId is undefined', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 7d38e3b732fc0..1d06f07bc774b 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -6,21 +6,15 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; -import React, { useEffect, useMemo, useState, useRef } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; -import { useDispatch } from 'react-redux'; import { Direction } from '../../../../common/search_strategy'; import { BrowserFields, DocValueFields } from '../../containers/source'; import { useTimelineEvents } from '../../../timelines/containers'; -import { timelineActions } from '../../../timelines/store/timeline'; import { useKibana } from '../../lib/kibana'; -import { - ColumnHeaderOptions, - KqlMode, - TimelineTabs, -} from '../../../timelines/store/timeline/model'; +import { ColumnHeaderOptions, KqlMode } from '../../../timelines/store/timeline/model'; import { HeaderSection } from '../header_section'; import { defaultHeaders } from '../../../timelines/components/timeline/body/column_headers/default_headers'; import { Sort } from '../../../timelines/components/timeline/body/sort'; @@ -45,7 +39,11 @@ import { inputsModel } from '../../store'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; import { ExitFullScreen } from '../exit_full_screen'; import { useGlobalFullScreen } from '../../containers/use_full_screen'; -import { TimelineExpandedEvent, TimelineId } from '../../../../common/types/timeline'; +import { + TimelineExpandedEventType, + TimelineId, + TimelineTabs, +} from '../../../../common/types/timeline'; import { GraphOverlay } from '../../../timelines/components/graph_overlay'; import { SELECTOR_TIMELINE_GLOBAL_CONTAINER } from '../../../timelines/components/timeline/styles'; @@ -114,7 +112,7 @@ interface Props { deletedEventIds: Readonly; docValueFields: DocValueFields[]; end: string; - expandedEvent: TimelineExpandedEvent; + expandedEvent: TimelineExpandedEventType; filters: Filter[]; headerFilterGroup?: React.ReactNode; height?: number; @@ -160,7 +158,6 @@ const EventsViewerComponent: React.FC = ({ utilityBar, graphEventId, }) => { - const dispatch = useDispatch(); const { globalFullScreen } = useGlobalFullScreen(); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; const kibana = useKibana(); @@ -191,9 +188,6 @@ const EventsViewerComponent: React.FC = ({ [justTitle] ); - const prevCombinedQueries = useRef<{ - filterQuery: string; - } | null>(null); const combinedQueries = combineQueries({ config: esQuery.getEsQueryConfig(kibana.services.uiSettings), dataProviders, @@ -220,12 +214,6 @@ const EventsViewerComponent: React.FC = ({ queryFields, ]); - const prevSortField = useRef< - Array<{ - field: string; - direction: Direction; - }> - >([]); const sortField = useMemo( () => sort.map(({ columnId, sortDirection }) => ({ @@ -251,17 +239,6 @@ const EventsViewerComponent: React.FC = ({ skip: !canQueryTimeline, }); - useEffect(() => { - if (!deepEqual(prevCombinedQueries.current, combinedQueries)) { - prevCombinedQueries.current = combinedQueries; - dispatch(timelineActions.toggleExpandedEvent({ timelineId: id })); - } - if (!deepEqual(prevSortField.current, sortField)) { - prevSortField.current = sortField; - dispatch(timelineActions.toggleExpandedEvent({ timelineId: id })); - } - }, [combinedQueries, dispatch, id, sortField]); - const totalCountMinusDeleted = useMemo( () => (totalCount > 0 ? totalCount - deletedEventIds.length : 0), [deletedEventIds.length, totalCount] diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 3272b0306f9c9..d7310ea776659 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -166,7 +166,7 @@ const makeMapStateToProps = () => { columns, dataProviders, deletedEventIds, - expandedEvent, + expandedEvent: expandedEvent?.query ?? {}, excludedRowRendererIds, filters: getGlobalFiltersQuerySelector(state), id, diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/mock.ts b/x-pack/plugins/security_solution/public/common/components/events_viewer/mock.ts index d2bd940dcc266..153992d9f1adb 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/mock.ts +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/mock.ts @@ -12,3 +12,91 @@ export const mockEventViewerResponse = { }, events: [], }; + +export const mockEventViewerResponseWithEvents = { + totalCount: 1, + pageInfo: { + activePage: 0, + fakeTotalCount: 100, + }, + events: [ + { + ecs: { + _id: 'yb8TkHYBRgU82_bJu_rY', + timestamp: '2020-12-23T14:49:39.957Z', + _index: 'auditbeat-7.10.1-2020.12.18-000001', + '@timestamp': ['2020-12-23T14:49:39.957Z'], + event: { + module: ['system'], + action: ['process_started'], + category: ['process'], + dataset: ['process'], + kind: ['event'], + type: ['start'], + }, + host: { + name: ['handsome'], + os: { + family: ['darwin'], + }, + id: ['33'], + ip: ['0.0.0.0'], + }, + user: { + name: ['handsome'], + }, + message: ['Process node (PID: 77895) by user handsome STARTED'], + agent: { + type: ['auditbeat'], + }, + process: { + hash: { + sha1: ['`12345678987654323456Y7U87654`'], + }, + pid: ['77895'], + name: ['node'], + ppid: ['73537'], + args: [ + '/Users/handsome/.nvm/versions/node/v14.15.3/bin/node', + '/Users/handsome/Documents/workspace/kibana/node_modules/jest-worker/build/workers/processChild.js', + ], + entity_id: ['3arNfOyR9NwR2u03'], + executable: ['/Users/handsome/.nvm/versions/node/v14.15.3/bin/node'], + working_directory: ['/Users/handsome/Documents/workspace/kibana/x-pack'], + }, + }, + data: [ + { + field: '@timestamp', + value: ['2020-12-23T14:49:39.957Z'], + }, + { + field: 'event.module', + value: ['system'], + }, + { + field: 'event.action', + value: ['process_started'], + }, + { + field: 'host.name', + value: ['handsome'], + }, + { + field: 'user.name', + value: ['handsome'], + }, + { + field: 'message', + value: ['Process node (PID: 77895) by user handsome STARTED'], + }, + { + field: 'event.dataset', + value: ['process'], + }, + ], + _id: 'yb8TkHYBRgU82_bJu_rY', + _index: 'auditbeat-7.10.1-2020.12.18-000001', + }, + ], +}; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index 36cdc807c4c0c..891e7bfffe868 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -11,7 +11,7 @@ import { HostsTableType } from '../../../../hosts/store/model'; import { RouteSpyState, SiemRouteType } from '../../../utils/route/types'; import { TabNavigationProps } from '../tab_navigation/types'; import { NetworkRouteType } from '../../../../network/pages/navigation/types'; -import { TimelineTabs } from '../../../../timelines/store/timeline/model'; +import { TimelineTabs } from '../../../../../common/types/timeline'; const setBreadcrumbsMock = jest.fn(); const chromeMock = { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx index 158da3be3bbf7..f2fbe48c97c83 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx @@ -14,7 +14,7 @@ import { navTabs } from '../../../app/home/home_navigations'; import { HostsTableType } from '../../../hosts/store/model'; import { RouteSpyState } from '../../utils/route/types'; import { SiemNavigationProps, SiemNavigationComponentProps } from './types'; -import { TimelineTabs } from '../../../timelines/store/timeline/model'; +import { TimelineTabs } from '../../../../common/types/timeline'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx index f4ffc25146be5..e5c011cdc14be 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx @@ -6,12 +6,12 @@ import { mount } from 'enzyme'; import React from 'react'; +import { TimelineTabs } from '../../../../../common/types/timeline'; import { navTabs } from '../../../../app/home/home_navigations'; import { SecurityPageName } from '../../../../app/types'; import { navTabsHostDetails } from '../../../../hosts/pages/details/nav_tabs'; import { HostsTableType } from '../../../../hosts/store/model'; -import { TimelineTabs } from '../../../../timelines/store/timeline/model'; import { RouteSpyState } from '../../../utils/route/types'; import { CONSTANTS } from '../../url_state/constants'; import { TabNavigationComponent } from './'; diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts index 9932e52b6a1d1..9f51ecf9483b2 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts @@ -12,11 +12,11 @@ import * as H from 'history'; import { Query, Filter } from '../../../../../../../src/plugins/data/public'; import { url } from '../../../../../../../src/plugins/kibana_utils/public'; -import { TimelineId } from '../../../../common/types/timeline'; +import { TimelineId, TimelineTabs } from '../../../../common/types/timeline'; import { SecurityPageName } from '../../../app/types'; import { inputsSelectors, State } from '../../store'; import { UrlInputsModel } from '../../store/inputs/model'; -import { TimelineTabs, TimelineUrl } from '../../../timelines/store/timeline/model'; +import { TimelineUrl } from '../../../timelines/store/timeline/model'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { formatDate } from '../super_date_picker'; import { NavTab } from '../navigation/types'; diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts index bf5b6b1719605..d835636aa2778 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts @@ -17,7 +17,7 @@ import { Query } from '../../../../../../../src/plugins/data/public'; import { networkModel } from '../../../network/store'; import { hostsModel } from '../../../hosts/store'; import { HostsTableType } from '../../../hosts/store/model'; -import { TimelineTabs } from '../../../timelines/store/timeline/model'; +import { TimelineTabs } from '../../../../common/types/timeline'; type Action = 'PUSH' | 'POP' | 'REPLACE'; const pop: Action = 'POP'; diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index db21847991534..320c3a0736540 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -24,13 +24,12 @@ import { DEFAULT_INDEX_PATTERN, } from '../../../common/constants'; import { networkModel } from '../../network/store'; -import { TimelineType, TimelineStatus } from '../../../common/types/timeline'; +import { TimelineType, TimelineStatus, TimelineTabs } from '../../../common/types/timeline'; import { mockManagementState } from '../../management/store/reducer'; import { ManagementState } from '../../management/types'; import { initialSourcererState, SourcererScopeName } from '../store/sourcerer/model'; import { mockBrowserFields, mockDocValueFields } from '../containers/source/mock'; import { mockIndexPattern } from './index_pattern'; -import { TimelineTabs } from '../../timelines/store/timeline/model'; export const mockGlobalState: State = { app: { diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index c8d9fc981d880..03109803eb9d8 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -5,14 +5,19 @@ */ import { FilterStateStore } from '../../../../../../src/plugins/data/common/es_query/filters/meta_filter'; -import { TimelineId, TimelineType, TimelineStatus } from '../../../common/types/timeline'; +import { + TimelineId, + TimelineType, + TimelineStatus, + TimelineTabs, +} from '../../../common/types/timeline'; import { OpenTimelineResult } from '../../timelines/components/open_timeline/types'; import { GetAllTimeline, SortFieldTimeline, TimelineResult, Direction } from '../../graphql/types'; import { TimelineEventsDetailsItem } from '../../../common/search_strategy'; import { allTimelinesQuery } from '../../timelines/containers/all/index.gql_query'; import { CreateTimelineProps } from '../../detections/components/alerts_table/types'; -import { TimelineModel, TimelineTabs } from '../../timelines/store/timeline/model'; +import { TimelineModel } from '../../timelines/store/timeline/model'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; export interface MockedProvidedQuery { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index d251cce381536..64e916f87b09d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -19,10 +19,14 @@ import { } from '../../../common/mock/'; import { CreateTimeline, UpdateTimelineLoading } from './types'; import { Ecs } from '../../../../common/ecs'; -import { TimelineId, TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { + TimelineId, + TimelineType, + TimelineStatus, + TimelineTabs, +} from '../../../../common/types/timeline'; import { ISearchStart } from '../../../../../../../src/plugins/data/public'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; -import { TimelineTabs } from '../../../timelines/store/timeline/model'; jest.mock('apollo-client'); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.test.tsx index c9bd1ee13cd95..f9bb8b58634fa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.test.tsx @@ -8,7 +8,7 @@ import { mount } from 'enzyme'; import React from 'react'; import { TestProviders } from '../../../../common/mock/test_providers'; -import { TimelineTabs } from '../../../store/timeline/model'; +import { TimelineTabs } from '../../../../../common/types/timeline'; import { FlyoutBottomBar } from '.'; describe('FlyoutBottomBar', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx index e6de34f1bf7a4..edc571528e94a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx @@ -14,7 +14,7 @@ import { DataProvider } from '../../timeline/data_providers/data_provider'; import { flattenIntoAndGroups } from '../../timeline/data_providers/helpers'; import { DataProviders } from '../../timeline/data_providers'; import { FlyoutHeaderPanel } from '../header'; -import { TimelineTabs } from '../../../store/timeline/model'; +import { TimelineTabs } from '../../../../../common/types/timeline'; export const FLYOUT_BUTTON_CLASS_NAME = 'timeline-flyout-button'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx index e22a6616ecfc6..73c2eae1402c0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx @@ -20,7 +20,7 @@ import styled from 'styled-components'; import { FormattedRelative } from '@kbn/i18n/react'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; +import { TimelineStatus, TimelineTabs, TimelineType } from '../../../../../common/types/timeline'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; import { timelineDefaults } from '../../../../timelines/store/timeline/defaults'; import { AddToFavoritesButton } from '../../timeline/properties/helpers'; @@ -33,7 +33,6 @@ import { ActiveTimelines } from './active_timelines'; import * as i18n from './translations'; import * as commonI18n from '../../timeline/properties/translations'; import { getTimelineStatusByIdSelector } from './selectors'; -import { TimelineTabs } from '../../../store/timeline/model'; // to hide side borders const StyledPanel = styled(EuiPanel)` diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx index 622efefc6230a..6881ad3ee4bc1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx @@ -11,10 +11,9 @@ import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import { AppLeaveHandler } from '../../../../../../../src/core/public'; -import { TimelineId, TimelineStatus } from '../../../../common/types/timeline'; +import { TimelineId, TimelineStatus, TimelineTabs } from '../../../../common/types/timeline'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { timelineActions } from '../../store/timeline'; -import { TimelineTabs } from '../../store/timeline/model'; import { FlyoutBottomBar } from './bottom_bar'; import { Pane } from './pane'; import { getTimelineShowStatusByIdSelector } from './selectors'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/selectors.ts b/x-pack/plugins/security_solution/public/timelines/components/flyout/selectors.ts index 0ec4fecedfa7f..e6892c121ed44 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/selectors.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/selectors.ts @@ -6,9 +6,8 @@ import { createSelector } from 'reselect'; -import { TimelineStatus } from '../../../../common/types/timeline'; +import { TimelineStatus, TimelineTabs } from '../../../../common/types/timeline'; import { timelineSelectors } from '../../store/timeline'; -import { TimelineTabs } from '../../store/timeline/model'; export const getTimelineShowStatusByIdSelector = () => createSelector(timelineSelectors.selectTimeline, (timeline) => ({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 61b0c004dcb9d..da6eec968d11c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -39,12 +39,16 @@ import { KueryFilterQueryKind } from '../../../common/store/model'; import { Note } from '../../../common/lib/note'; import moment from 'moment'; import sinon from 'sinon'; -import { TimelineId, TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { + TimelineId, + TimelineType, + TimelineStatus, + TimelineTabs, +} from '../../../../common/types/timeline'; import { mockTimeline as mockSelectedTimeline, mockTemplate as mockSelectedTemplate, } from './__mocks__'; -import { TimelineTabs } from '../../store/timeline/model'; jest.mock('../../../common/store/inputs/actions'); jest.mock('../../../common/components/url_state/normalize_time_range.ts'); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index 37de75fd736af..c7821df347311 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -27,6 +27,7 @@ import { TimelineId, TimelineStatus, TimelineType, + TimelineTabs, } from '../../../../common/types/timeline'; import { @@ -42,11 +43,7 @@ import { addTimeline as dispatchAddTimeline, addNote as dispatchAddGlobalTimelineNote, } from '../../../timelines/store/timeline/actions'; -import { - ColumnHeaderOptions, - TimelineModel, - TimelineTabs, -} from '../../../timelines/store/timeline/model'; +import { ColumnHeaderOptions, TimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx index fc05e61442e83..d35a5f487ed8e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx @@ -17,6 +17,7 @@ import { MarkdownRenderer } from '../../../../common/components/markdown_editor' import { timelineActions } from '../../../store/timeline'; import { NOTE_CONTENT_CLASS_NAME } from '../../timeline/body/helpers'; import * as i18n from './translations'; +import { TimelineTabs } from '../../../../../common/types/timeline'; export const NotePreviewsContainer = styled.section` padding-top: ${({ theme }) => `${theme.eui.euiSizeS}`}; @@ -37,6 +38,7 @@ const ToggleEventDetailsButtonComponent: React.FC const handleClick = useCallback(() => { dispatch( timelineActions.toggleExpandedEvent({ + tabType: TimelineTabs.notes, timelineId, event: { eventId, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx index 0242a2a0ff091..21ca30658f530 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx @@ -11,7 +11,8 @@ import { getOr } from 'lodash/fp'; import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME } from '../../../../../common/components/drag_and_drop/helpers'; import { Ecs } from '../../../../../../common/ecs'; import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline'; -import { ColumnHeaderOptions, TimelineTabs } from '../../../../../timelines/store/timeline/model'; +import { TimelineTabs } from '../../../../../../common/types/timeline'; +import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model'; import { ARIA_COLUMN_INDEX_OFFSET } from '../../helpers'; import { EventsTd, EVENTS_TD_CLASS_NAME, EventsTdContent, EventsTdGroupData } from '../../styles'; import { ColumnRenderer } from '../renderers/column_renderer'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index 0525767e616be..cff3d2890d85a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -11,7 +11,7 @@ import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants'; import * as i18n from '../translations'; import { EventColumnView } from './event_column_view'; -import { TimelineType } from '../../../../../../common/types/timeline'; +import { TimelineTabs, TimelineType } from '../../../../../../common/types/timeline'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; jest.mock('../../../../../common/hooks/use_selector'); @@ -48,6 +48,7 @@ describe('EventColumnView', () => { selectedEventIds: {}, showCheckboxes: false, showNotes: false, + tabType: TimelineTabs.query, timelineId: 'timeline-test', toggleShowNotes: jest.fn(), updateNote: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx index ae8d2a47c7dc7..4e61fb7346c5c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx @@ -9,7 +9,7 @@ import React, { useCallback, useMemo } from 'react'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; import { Ecs } from '../../../../../../common/ecs'; import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline'; -import { ColumnHeaderOptions, TimelineTabs } from '../../../../../timelines/store/timeline/model'; +import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model'; import { OnPinEvent, OnRowSelected, OnUnPinEvent } from '../../events'; import { EventsTrData } from '../../styles'; import { Actions } from '../actions'; @@ -26,7 +26,7 @@ import { InvestigateInTimelineAction } from '../../../../../detections/component import { AddEventNoteAction } from '../actions/add_note_icon_item'; import { PinEventAction } from '../actions/pin_event_action'; import { inputsModel } from '../../../../../common/store'; -import { TimelineId } from '../../../../../../common/types/timeline'; +import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; import { timelineSelectors } from '../../../../store/timeline'; import { timelineDefaults } from '../../../../store/timeline/defaults'; import { AddToCaseAction } from '../../../../../cases/components/timeline_actions/add_to_case_action'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx index 92ae01b185f7a..dba08823b87fe 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx @@ -12,7 +12,8 @@ import { TimelineItem, TimelineNonEcsData, } from '../../../../../../common/search_strategy/timeline'; -import { ColumnHeaderOptions, TimelineTabs } from '../../../../../timelines/store/timeline/model'; +import { TimelineTabs } from '../../../../../../common/types/timeline'; +import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model'; import { OnRowSelected } from '../../events'; import { EventsTbody } from '../../styles'; import { ColumnRenderer } from '../renderers/column_renderer'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index e3f5a744e8b7d..f00b86ef96567 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -8,13 +8,13 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; -import { TimelineId } from '../../../../../../common/types/timeline'; +import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; import { BrowserFields } from '../../../../../common/containers/source'; import { TimelineItem, TimelineNonEcsData, } from '../../../../../../common/search_strategy/timeline'; -import { ColumnHeaderOptions, TimelineTabs } from '../../../../../timelines/store/timeline/model'; +import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model'; import { OnPinEvent, OnRowSelected } from '../../events'; import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../../helpers'; import { EventsTrGroup, EventsTrSupplement, EventsTrSupplementContainer } from '../../styles'; @@ -92,7 +92,10 @@ const StatefulEventComponent: React.FC = ({ const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({}); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const expandedEvent = useDeepEqualSelector( - (state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedEvent + (state) => + (getTimeline(state, timelineId) ?? timelineDefaults).expandedEvent[ + tabType ?? TimelineTabs.query + ] ?? {} ); const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []); const notesById = useDeepEqualSelector(getNotesByIds); @@ -153,6 +156,7 @@ const StatefulEventComponent: React.FC = ({ dispatch( timelineActions.toggleExpandedEvent({ + tabType, timelineId, event: { eventId, @@ -161,10 +165,10 @@ const StatefulEventComponent: React.FC = ({ }) ); - if (timelineId === TimelineId.active) { + if (timelineId === TimelineId.active && tabType === TimelineTabs.query) { activeTimeline.toggleExpandedEvent({ eventId, indexName }); } - }, [dispatch, event._id, event._index, timelineId]); + }, [dispatch, event._id, event._index, tabType, timelineId]); const associateNote = useCallback( (noteId: string) => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index 0295d44b646d7..3a738db981b38 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -16,12 +16,11 @@ import { TimelineTypeLiteral, TimelineType, TimelineId, + TimelineTabs, } from '../../../../../common/types/timeline'; import { OnPinEvent, OnUnPinEvent } from '../events'; import { ActionIconItem } from './actions/action_icon_item'; - import * as i18n from './translations'; -import { TimelineTabs } from '../../../store/timeline/model'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const omitTypenameAndEmpty = (k: string, v: any): any | undefined => diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index c8e911db85f64..cc04b83382998 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -17,7 +17,7 @@ import { BodyComponent, StatefulBodyProps } from '.'; import { Sort } from './sort'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; -import { TimelineTabs } from '../../../store/timeline/model'; +import { TimelineTabs } from '../../../../../common/types/timeline'; const mockSort: Sort[] = [ { @@ -221,4 +221,78 @@ describe('Body', () => { ); }); }); + + describe('event details', () => { + beforeEach(() => { + mockDispatch.mockReset(); + }); + test('call the right reduce action to show event details for query tab', async () => { + const wrapper = mount( + + + + ); + + wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click'); + wrapper.update(); + expect(mockDispatch).toBeCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual({ + payload: { + event: { + eventId: '1', + indexName: undefined, + }, + tabType: 'query', + timelineId: 'timeline-test', + }, + type: 'x-pack/security_solution/local/timeline/TOGGLE_EXPANDED_EVENT', + }); + }); + + test('call the right reduce action to show event details for pinned tab', async () => { + const wrapper = mount( + + + + ); + + wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click'); + wrapper.update(); + expect(mockDispatch).toBeCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual({ + payload: { + event: { + eventId: '1', + indexName: undefined, + }, + tabType: 'pinned', + timelineId: 'timeline-test', + }, + type: 'x-pack/security_solution/local/timeline/TOGGLE_EXPANDED_EVENT', + }); + }); + + test('call the right reduce action to show event details for notes tab', async () => { + const wrapper = mount( + + + + ); + + wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click'); + wrapper.update(); + expect(mockDispatch).toBeCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual({ + payload: { + event: { + eventId: '1', + indexName: undefined, + }, + tabType: 'notes', + timelineId: 'timeline-test', + }, + type: 'x-pack/security_solution/local/timeline/TOGGLE_EXPANDED_EVENT', + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 4a33d0d3af33e..a03f4c07645ad 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -10,7 +10,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; -import { RowRendererId, TimelineId } from '../../../../../common/types/timeline'; +import { RowRendererId, TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { FIRST_ARIA_INDEX, ARIA_COLINDEX_ATTRIBUTE, @@ -21,7 +21,7 @@ import { BrowserFields } from '../../../../common/containers/source'; import { TimelineItem } from '../../../../../common/search_strategy/timeline'; import { inputsModel, State } from '../../../../common/store'; import { useManageTimeline } from '../../manage_timeline'; -import { ColumnHeaderOptions, TimelineModel, TimelineTabs } from '../../../store/timeline/model'; +import { ColumnHeaderOptions, TimelineModel } from '../../../store/timeline/model'; import { timelineDefaults } from '../../../store/timeline/defaults'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; import { OnRowSelected, OnSelectAll } from '../events'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index 9895f4eda0e6c..c75f8a0d1c170 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -25,10 +25,12 @@ import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { useTimelineEventsDetails } from '../../containers/details'; import { timelineSelectors } from '../../store/timeline'; import { timelineDefaults } from '../../store/timeline/defaults'; +import { TimelineTabs } from '../../../../common/types/timeline'; interface EventDetailsProps { browserFields: BrowserFields; docValueFields: DocValueFields[]; + tabType: TimelineTabs; timelineId: string; handleOnEventClosed?: HandleOnEventClosed; } @@ -36,12 +38,13 @@ interface EventDetailsProps { const EventDetailsComponent: React.FC = ({ browserFields, docValueFields, + tabType, timelineId, handleOnEventClosed, }) => { const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const expandedEvent = useDeepEqualSelector( - (state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedEvent + (state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedEvent[tabType] ?? {} ); const [loading, detailsData] = useTimelineEventsDetails({ @@ -71,6 +74,7 @@ const EventDetailsComponent: React.FC = ({ isAlert={isAlert} loading={loading} timelineId={timelineId} + timelineTabType={tabType} /> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index df8e84b4e2a78..a38fde0e3f548 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -20,7 +20,7 @@ import { import React, { useMemo, useState } from 'react'; import styled from 'styled-components'; -import { TimelineExpandedEvent } from '../../../../../common/types/timeline'; +import { TimelineExpandedEventType, TimelineTabs } from '../../../../../common/types/timeline'; import { BrowserFields } from '../../../../common/containers/source'; import { EventDetails, @@ -35,9 +35,10 @@ export type HandleOnEventClosed = () => void; interface Props { browserFields: BrowserFields; detailsData: TimelineEventsDetailsItem[] | null; - event: TimelineExpandedEvent; + event: TimelineExpandedEventType; isAlert: boolean; loading: boolean; + timelineTabType: TimelineTabs | 'flyout'; timelineId: string; } @@ -71,7 +72,7 @@ export const ExpandableEventTitle = React.memo( ExpandableEventTitle.displayName = 'ExpandableEventTitle'; export const ExpandableEvent = React.memo( - ({ browserFields, event, timelineId, isAlert, loading, detailsData }) => { + ({ browserFields, event, timelineId, timelineTabType, isAlert, loading, detailsData }) => { const [view, setView] = useState(EventsViewType.summaryView); const message = useMemo(() => { @@ -116,6 +117,7 @@ export const ExpandableEvent = React.memo( id={event.eventId!} isAlert={isAlert} onViewSelected={setView} + timelineTabType={timelineTabType} timelineId={timelineId} view={view} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx index 41ac16a12e648..2b26e3f9eb0b5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -17,7 +17,7 @@ import { isTab } from '../../../common/components/accessibility/helpers'; import { useSourcererScope } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { FlyoutHeader, FlyoutHeaderPanel } from '../flyout/header'; -import { TimelineType } from '../../../../common/types/timeline'; +import { TimelineType, TimelineTabs } from '../../../../common/types/timeline'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { activeTimeline } from '../../containers/active_timeline_context'; import { EVENTS_COUNT_BUTTON_CLASS_NAME, onTimelineTabKeyPressed } from './helpers'; @@ -68,7 +68,9 @@ const StatefulTimelineComponent: React.FC = ({ timelineId }) => { id: timelineId, columns: defaultHeaders, indexNames: selectedPatterns, - expandedEvent: activeTimeline.getExpandedEvent(), + expandedEvent: { + [TimelineTabs.query]: activeTimeline.getExpandedEvent(), + }, show: false, }) ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx index bfb990cbd7364..34e5aed885d5c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { filter, pick, uniqBy } from 'lodash/fp'; +import { filter, uniqBy } from 'lodash/fp'; import { EuiAvatar, EuiFlexGroup, @@ -21,17 +21,17 @@ import styled from 'styled-components'; import { useSourcererScope } from '../../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import { timelineActions, timelineSelectors } from '../../../store/timeline'; +import { timelineActions } from '../../../store/timeline'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { TimelineStatus } from '../../../../../common/types/timeline'; +import { TimelineStatus, TimelineTabs } from '../../../../../common/types/timeline'; import { appSelectors } from '../../../../common/store/app'; -import { timelineDefaults } from '../../../store/timeline/defaults'; import { AddNote } from '../../notes/add_note'; import { CREATED_BY, NOTES } from '../../notes/translations'; import { PARTICIPANTS } from '../../../../cases/translations'; import { NotePreviews } from '../../open_timeline/note_previews'; import { TimelineResultNote } from '../../open_timeline/types'; import { EventDetails } from '../event_details'; +import { getTimelineNoteSelector } from './selectors'; const FullWidthFlexGroup = styled(EuiFlexGroup)` width: 100%; @@ -121,18 +121,14 @@ interface NotesTabContentProps { const NotesTabContentComponent: React.FC = ({ timelineId }) => { const dispatch = useDispatch(); - const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const getTimelineNotes = useMemo(() => getTimelineNoteSelector(), []); const { createdBy, expandedEvent, eventIdToNoteIds, + noteIds, status: timelineStatus, - } = useDeepEqualSelector((state) => - pick( - ['createdBy', 'expandedEvent', 'eventIdToNoteIds', 'status'], - getTimeline(state, timelineId) ?? timelineDefaults - ) - ); + } = useDeepEqualSelector((state) => getTimelineNotes(state, timelineId)); const { browserFields, docValueFields } = useSourcererScope(SourcererScopeName.timeline); @@ -142,7 +138,20 @@ const NotesTabContentComponent: React.FC = ({ timelineId } ); const [newNote, setNewNote] = useState(''); const isImmutable = timelineStatus === TimelineStatus.immutable; - const notes: TimelineResultNote[] = useDeepEqualSelector(getNotesAsCommentsList); + const appNotes: TimelineResultNote[] = useDeepEqualSelector(getNotesAsCommentsList); + + const allTimelineNoteIds = useMemo(() => { + const eventNoteIds = Object.values(eventIdToNoteIds).reduce( + (acc, v) => [...acc, ...v], + [] + ); + return [...noteIds, ...eventNoteIds]; + }, [noteIds, eventIdToNoteIds]); + + const notes = useMemo( + () => appNotes.filter((appNote) => allTimelineNoteIds.includes(appNote?.noteId ?? '-1')), + [appNotes, allTimelineNoteIds] + ); // filter for savedObjectId to make sure we don't display `elastic` user while saving the note const participants = useMemo(() => uniqBy('updatedBy', filter('savedObjectId', notes)), [notes]); @@ -153,20 +162,21 @@ const NotesTabContentComponent: React.FC = ({ timelineId } ); const handleOnEventClosed = useCallback(() => { - dispatch(timelineActions.toggleExpandedEvent({ timelineId })); + dispatch(timelineActions.toggleExpandedEvent({ tabType: TimelineTabs.notes, timelineId })); }, [dispatch, timelineId]); const EventDetailsContent = useMemo( () => - expandedEvent.eventId ? ( + expandedEvent?.eventId != null ? ( ) : null, - [browserFields, docValueFields, expandedEvent.eventId, handleOnEventClosed, timelineId] + [browserFields, docValueFields, expandedEvent, handleOnEventClosed, timelineId] ); const SidebarContent = useMemo( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/selectors.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/selectors.ts new file mode 100644 index 0000000000000..37ee980b1a4ae --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/selectors.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createSelector } from 'reselect'; + +import { timelineSelectors } from '../../../store/timeline'; + +export const getTimelineNoteSelector = () => + createSelector(timelineSelectors.selectTimeline, (timeline) => { + return { + createdBy: timeline.createdBy, + expandedEvent: timeline.expandedEvent?.notes ?? {}, + eventIdToNoteIds: timeline?.eventIdToNoteIds ?? {}, + noteIds: timeline.noteIds, + status: timeline.status, + }; + }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx index a0d2ca57f90b3..1054b5405d9d9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx @@ -23,11 +23,12 @@ import { EventDetailsWidthProvider } from '../../../../common/components/events_ import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineDefaults } from '../../../store/timeline/defaults'; import { useSourcererScope } from '../../../../common/containers/sourcerer'; -import { TimelineModel, TimelineTabs } from '../../../store/timeline/model'; +import { TimelineModel } from '../../../store/timeline/model'; import { EventDetails } from '../event_details'; import { ToggleExpandedEvent } from '../../../store/timeline/actions'; import { State } from '../../../../common/store'; import { calculateTotalPages } from '../helpers'; +import { TimelineTabs } from '../../../../../common/types/timeline'; const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` overflow-y: hidden; @@ -167,7 +168,7 @@ export const PinnedTabContentComponent: React.FC = ({ }); const handleOnEventClosed = useCallback(() => { - onEventClosed({ timelineId }); + onEventClosed({ tabType: TimelineTabs.pinned, timelineId }); }, [timelineId, onEventClosed]); return ( @@ -218,6 +219,7 @@ export const PinnedTabContentComponent: React.FC = ({ @@ -248,7 +250,7 @@ const makeMapStateToProps = () => { itemsPerPage, itemsPerPageOptions, pinnedEventIds, - showEventDetails: !!expandedEvent.eventId, + showEventDetails: !!expandedEvent[TimelineTabs.pinned]?.eventId, sort, }; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index 4769c826a2fad..b24a4afcbeea2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -17,12 +17,11 @@ import { QueryTabContentComponent, Props as QueryTabContentComponentProps } from import { Sort } from '../body/sort'; import { mockDataProviders } from '../data_providers/mock/mock_data_providers'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; -import { TimelineId, TimelineStatus } from '../../../../../common/types/timeline'; +import { TimelineId, TimelineStatus, TimelineTabs } from '../../../../../common/types/timeline'; import { useTimelineEvents } from '../../../containers/index'; import { useTimelineEventsDetails } from '../../../containers/details/index'; import { useSourcererScope } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; -import { TimelineTabs } from '../../../store/timeline/model'; jest.mock('../../../containers/index', () => ({ useTimelineEvents: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index c0840d58174b3..d4c03117adcb9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -14,7 +14,7 @@ import { EuiBadge, } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; -import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react'; +import React, { useState, useMemo, useEffect, useCallback } from 'react'; import styled from 'styled-components'; import { Dispatch } from 'redux'; import { connect, ConnectedProps } from 'react-redux'; @@ -33,7 +33,7 @@ import { calculateTotalPages, combineQueries } from '../helpers'; import { TimelineRefetch } from '../refetch_timeline'; import { esQuery, FilterManager } from '../../../../../../../../src/plugins/data/public'; import { useManageTimeline } from '../../manage_timeline'; -import { TimelineEventsType, TimelineId } from '../../../../../common/types/timeline'; +import { TimelineEventsType, TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { requiredFieldsForActions } from '../../../../detections/components/alerts_table/default_config'; import { SuperDatePicker } from '../../../../common/components/super_date_picker'; import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context'; @@ -44,7 +44,7 @@ import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineDefaults } from '../../../../timelines/store/timeline/defaults'; import { useSourcererScope } from '../../../../common/containers/sourcerer'; import { useTimelineEventsCountPortal } from '../../../../common/hooks/use_timeline_events_count'; -import { TimelineModel, TimelineTabs } from '../../../../timelines/store/timeline/model'; +import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { EventDetails } from '../event_details'; import { TimelineDatePickerLock } from '../date_picker_lock'; import { HideShowContainer } from '../styles'; @@ -173,9 +173,6 @@ export const QueryTabContentComponent: React.FC = ({ kqlQueryExpression, ]); - const prevCombinedQueries = useRef<{ - filterQuery: string; - } | null>(null); const combinedQueries = useMemo( () => combineQueries({ @@ -211,12 +208,7 @@ export const QueryTabContentComponent: React.FC = ({ return [...columnFields, ...requiredFieldsForActions]; }, [columns]); - const prevTimelineQuerySortField = useRef< - Array<{ - field: string; - direction: Direction; - }> - >([]); + const timelineQuerySortField = useMemo( () => sort.map(({ columnId, sortDirection }) => ({ @@ -252,7 +244,7 @@ export const QueryTabContentComponent: React.FC = ({ }); const handleOnEventClosed = useCallback(() => { - onEventClosed({ timelineId }); + onEventClosed({ tabType: TimelineTabs.query, timelineId }); if (timelineId === TimelineId.active) { activeTimeline.toggleExpandedEvent({ @@ -266,17 +258,6 @@ export const QueryTabContentComponent: React.FC = ({ setIsTimelineLoading({ id: timelineId, isLoading: isQueryLoading || loadingSourcerer }); }, [loadingSourcerer, timelineId, isQueryLoading, setIsTimelineLoading]); - useEffect(() => { - if (!deepEqual(prevCombinedQueries.current, combinedQueries)) { - prevCombinedQueries.current = combinedQueries; - handleOnEventClosed(); - } - if (!deepEqual(prevTimelineQuerySortField.current, timelineQuerySortField)) { - prevTimelineQuerySortField.current = timelineQuerySortField; - handleOnEventClosed(); - } - }, [combinedQueries, handleOnEventClosed, timelineQuerySortField]); - return ( <> @@ -368,6 +349,7 @@ export const QueryTabContentComponent: React.FC = ({ @@ -416,7 +398,7 @@ const makeMapStateToProps = () => { dataProviders, eventType: eventType ?? 'raw', end: input.timerange.to, - expandedEvent, + expandedEvent: expandedEvent[TimelineTabs.query] ?? {}, filters: timelineFilter, timelineId, isLive: input.policy.kind === 'interval', @@ -425,7 +407,7 @@ const makeMapStateToProps = () => { kqlMode, kqlQueryExpression, showCallOutUnauthorizedMsg: getShowCallOutUnauthorizedMsg(state), - showEventDetails: !!expandedEvent.eventId, + showEventDetails: !!expandedEvent[TimelineTabs.query]?.eventId, show, sort, start: input.timerange.from, 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 c97571fbbd6f3..25312ac2747ae 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 @@ -8,16 +8,21 @@ import { EuiBadge, EuiLoadingContent, EuiTabs, EuiTab } from '@elastic/eui'; import React, { lazy, memo, Suspense, useCallback, useEffect, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; +import { TimelineTabs } from '../../../../../common/types/timeline'; -import { useShallowEqualSelector } from '../../../../common/hooks/use_selector'; +import { + useShallowEqualSelector, + useDeepEqualSelector, +} from '../../../../common/hooks/use_selector'; import { TimelineEventsCountBadge } from '../../../../common/hooks/use_timeline_events_count'; import { timelineActions } from '../../../store/timeline'; -import { TimelineTabs } from '../../../store/timeline/model'; import { getActiveTabSelector, + getNoteIdsSelector, getNotesSelector, getPinnedEventSelector, getShowTimelineSelector, + getEventIdToNoteIdsSelector, } from './selectors'; import * as i18n from './translations'; @@ -137,37 +142,55 @@ const TabsContentComponent: React.FC = ({ timelineId, graphEve const getActiveTab = useMemo(() => getActiveTabSelector(), []); const getShowTimeline = useMemo(() => getShowTimelineSelector(), []); const getNumberOfPinnedEvents = useMemo(() => getPinnedEventSelector(), []); - const getNumberOfNotes = useMemo(() => getNotesSelector(), []); + const getAppNotes = useMemo(() => getNotesSelector(), []); + const getTimelineNoteIds = useMemo(() => getNoteIdsSelector(), []); + const getTimelinePinnedEventNotes = useMemo(() => getEventIdToNoteIdsSelector(), []); + const activeTab = useShallowEqualSelector((state) => getActiveTab(state, timelineId)); const showTimeline = useShallowEqualSelector((state) => getShowTimeline(state, timelineId)); const numberOfPinnedEvents = useShallowEqualSelector((state) => getNumberOfPinnedEvents(state, timelineId) ); - const numberOfNotes = useShallowEqualSelector((state) => getNumberOfNotes(state)); + const globalTimelineNoteIds = useDeepEqualSelector((state) => + getTimelineNoteIds(state, timelineId) + ); + const eventIdToNoteIds = useDeepEqualSelector((state) => + getTimelinePinnedEventNotes(state, timelineId) + ); + const appNotes = useDeepEqualSelector((state) => getAppNotes(state)); + + const allTimelineNoteIds = useMemo(() => { + const eventNoteIds = Object.values(eventIdToNoteIds).reduce( + (acc, v) => [...acc, ...v], + [] + ); + return [...globalTimelineNoteIds, ...eventNoteIds]; + }, [globalTimelineNoteIds, eventIdToNoteIds]); + + const numberOfNotes = useMemo( + () => appNotes.filter((appNote) => allTimelineNoteIds.includes(appNote.id)).length, + [appNotes, allTimelineNoteIds] + ); const setQueryAsActiveTab = useCallback(() => { - dispatch(timelineActions.toggleExpandedEvent({ timelineId })); dispatch( timelineActions.setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.query }) ); }, [dispatch, timelineId]); const setGraphAsActiveTab = useCallback(() => { - dispatch(timelineActions.toggleExpandedEvent({ timelineId })); dispatch( timelineActions.setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.graph }) ); }, [dispatch, timelineId]); const setNotesAsActiveTab = useCallback(() => { - dispatch(timelineActions.toggleExpandedEvent({ timelineId })); dispatch( timelineActions.setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.notes }) ); }, [dispatch, timelineId]); const setPinnedAsActiveTab = useCallback(() => { - dispatch(timelineActions.toggleExpandedEvent({ timelineId })); dispatch( timelineActions.setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.pinned }) ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/selectors.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/selectors.ts index 332785161b09a..ff65c35588a8d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/selectors.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/selectors.ts @@ -5,8 +5,8 @@ */ import { createSelector } from 'reselect'; +import { TimelineTabs } from '../../../../../common/types/timeline'; import { selectNotesById } from '../../../../common/store/app/selectors'; -import { TimelineTabs } from '../../../store/timeline/model'; import { selectTimeline } from '../../../store/timeline/selectors'; export const getActiveTabSelector = () => @@ -18,5 +18,11 @@ export const getShowTimelineSelector = () => export const getPinnedEventSelector = () => createSelector(selectTimeline, (timeline) => Object.keys(timeline?.pinnedEventIds ?? {}).length); +export const getNoteIdsSelector = () => + createSelector(selectTimeline, (timeline) => timeline?.noteIds ?? []); + +export const getEventIdToNoteIdsSelector = () => + createSelector(selectTimeline, (timeline) => timeline?.eventIdToNoteIds ?? {}); + export const getNotesSelector = () => - createSelector(selectNotesById, (notesById) => Object.keys(notesById ?? {}).length); + createSelector(selectNotesById, (notesById) => Object.values(notesById)); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts b/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts index 287fcd7f11e93..3d6d061157b29 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TimelineExpandedEvent } from '../../../common/types/timeline'; +import { TimelineExpandedEventType } from '../../../common/types/timeline'; import { TimelineEventsAllRequestOptions } from '../../../common/search_strategy/timeline'; import { TimelineArgs } from '.'; @@ -21,7 +21,7 @@ import { TimelineArgs } from '.'; class ActiveTimelineEvents { private _activePage: number = 0; - private _expandedEvent: TimelineExpandedEvent = {}; + private _expandedEvent: TimelineExpandedEventType = {}; private _pageName: string = ''; private _request: TimelineEventsAllRequestOptions | null = null; private _response: TimelineArgs | null = null; @@ -38,7 +38,7 @@ class ActiveTimelineEvents { return this._expandedEvent; } - toggleExpandedEvent(expandedEvent: TimelineExpandedEvent) { + toggleExpandedEvent(expandedEvent: TimelineExpandedEventType) { if (expandedEvent.eventId === this._expandedEvent.eventId) { this._expandedEvent = {}; } else { @@ -46,7 +46,7 @@ class ActiveTimelineEvents { } } - setExpandedEvent(expandedEvent: TimelineExpandedEvent) { + setExpandedEvent(expandedEvent: TimelineExpandedEventType) { this._expandedEvent = expandedEvent; } diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx index ebc86b3c5cf5e..556221f2d4bfd 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { noop } from 'lodash/fp'; +import { isEmpty, noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; @@ -18,6 +18,7 @@ import { TimelineEventsDetailsStrategyResponse, } from '../../../../common/search_strategy'; import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/public'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; export interface EventsArgs { detailsData: TimelineEventsDetailsItem[] | null; } @@ -50,7 +51,7 @@ export const useTimelineEventsDetails = ({ const timelineDetailsSearch = useCallback( (request: TimelineEventsDetailsRequestOptions | null) => { - if (request == null || skip) { + if (request == null || skip || isEmpty(request.eventId)) { return; } @@ -84,11 +85,13 @@ export const useTimelineEventsDetails = ({ searchSubscription$.unsubscribe(); } }, - error: () => { + error: (msg) => { if (!didCancel) { setLoading(false); } - notifications.toasts.addDanger('Failed to run search'); + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger('Failed to run search'); + } }, }); }; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index 487dc171f5994..aefeda04dd962 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -15,13 +15,15 @@ import { } from '../../../timelines/components/timeline/data_providers/data_provider'; import { SerializedFilterQuery } from '../../../common/store/types'; -import { KqlMode, TimelineModel, ColumnHeaderOptions, TimelineTabs } from './model'; +import { KqlMode, TimelineModel, ColumnHeaderOptions } from './model'; import { TimelineNonEcsData } from '../../../../common/search_strategy/timeline'; import { TimelineEventsType, - TimelineExpandedEvent, + TimelineExpandedEventType, TimelineTypeLiteral, RowRendererId, + TimelineExpandedEvent, + TimelineTabs, } from '../../../../common/types/timeline'; import { InsertTimeline } from './types'; @@ -36,8 +38,9 @@ export const addNoteToEvent = actionCreator<{ id: string; noteId: string; eventI ); export interface ToggleExpandedEvent { + event?: TimelineExpandedEventType; + tabType?: TimelineTabs; timelineId: string; - event?: TimelineExpandedEvent; } export const toggleExpandedEvent = actionCreator('TOGGLE_EXPANDED_EVENT'); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts index 211bba3cc47d2..fd0d6bd3a9aaa 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { TimelineType, TimelineStatus, TimelineTabs } from '../../../../common/types/timeline'; import { Direction } from '../../../graphql/types'; import { defaultHeaders } from '../../components/timeline/body/column_headers/default_headers'; import { normalizeTimeRange } from '../../../common/components/url_state/normalize_time_range'; -import { SubsetTimelineModel, TimelineModel, TimelineTabs } from './model'; +import { SubsetTimelineModel, TimelineModel } from './model'; // normalizeTimeRange uses getTimeRangeSettings which cannot be used outside Kibana context if the uiSettings is not false const { from: start, to: end } = normalizeTimeRange({ from: '', to: '' }, false); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts index d890fbe6a1069..ec9ded610417f 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts @@ -5,10 +5,10 @@ */ import { Filter, esFilters } from '../../../../../../../src/plugins/data/public'; -import { TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { TimelineType, TimelineStatus, TimelineTabs } from '../../../../common/types/timeline'; import { Direction } from '../../../graphql/types'; import { convertTimelineAsInput } from './epic'; -import { TimelineModel, TimelineTabs } from './model'; +import { TimelineModel } from './model'; describe('Epic Timeline', () => { describe('#convertTimelineAsInput ', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx index 3014ae8d19d32..513d61ea862fa 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx @@ -40,8 +40,7 @@ import { Direction } from '../../../graphql/types'; import { addTimelineInStorage } from '../../containers/local_storage'; import { isPageTimeline } from './epic_local_storage'; -import { TimelineId, TimelineStatus } from '../../../../common/types/timeline'; -import { TimelineTabs } from './model'; +import { TimelineId, TimelineStatus, TimelineTabs } from '../../../../common/types/timeline'; jest.mock('../../containers/local_storage'); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts index f3ff3fffa53b9..c385f21153780 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts @@ -17,11 +17,11 @@ import type { TimelineType, TimelineStatus, RowRendererId, + TimelineTabs, } from '../../../../common/types/timeline'; export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages export type KqlMode = 'filter' | 'search'; - export type ColumnHeaderType = 'not-filtered' | 'text-filter'; /** Uniquely identifies a column */ @@ -43,13 +43,6 @@ export interface ColumnHeaderOptions { width: number; } -export enum TimelineTabs { - query = 'query', - graph = 'graph', - notes = 'notes', - pinned = 'pinned', -} - export interface TimelineModel { /** The selected tab to displayed in the timeline */ activeTab: TimelineTabs; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts index 59d5800271b8a..4ae271ed7a491 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts @@ -5,7 +5,7 @@ */ import { cloneDeep } from 'lodash/fp'; -import { TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { TimelineType, TimelineStatus, TimelineTabs } from '../../../../common/types/timeline'; import { IS_OPERATOR, @@ -40,7 +40,7 @@ import { updateTimelineTitle, upsertTimelineColumn, } from './helpers'; -import { ColumnHeaderOptions, TimelineModel, TimelineTabs } from './model'; +import { ColumnHeaderOptions, TimelineModel } from './model'; import { timelineDefaults } from './defaults'; import { TimelineById } from './types'; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index 8ba4d54871266..2603c1c677956 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -103,7 +103,7 @@ import { } from './helpers'; import { TimelineState, EMPTY_TIMELINE_BY_ID } from './types'; -import { TimelineType } from '../../../../common/types/timeline'; +import { TimelineType, TimelineTabs } from '../../../../common/types/timeline'; export const initialTimelineState: TimelineState = { timelineById: EMPTY_TIMELINE_BY_ID, @@ -178,16 +178,22 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state, timelineById: addTimelineNoteToEvent({ id, noteId, eventId, timelineById: state.timelineById }), })) - .case(toggleExpandedEvent, (state, { timelineId, event = {} }) => ({ - ...state, - timelineById: { - ...state.timelineById, - [timelineId]: { - ...state.timelineById[timelineId], - expandedEvent: event, + .case(toggleExpandedEvent, (state, { tabType, timelineId, event = {} }) => { + const expandedTabType = tabType ?? TimelineTabs.query; + return { + ...state, + timelineById: { + ...state.timelineById, + [timelineId]: { + ...state.timelineById[timelineId], + expandedEvent: { + ...state.timelineById[timelineId].expandedEvent, + [expandedTabType]: event, + }, + }, }, - }, - })) + }; + }) .case(addProvider, (state, { id, provider }) => ({ ...state, timelineById: addTimelineProvider({ id, provider, timelineById: state.timelineById }), From 113634a66b1e8dc0fcd26512690a46eb5ec03fff Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Wed, 23 Dec 2020 21:44:57 -0500 Subject: [PATCH 49/72] [Security Solution] [Detections] Multiple timestamp fields (#86368) * query timestamp override and default @timestamp field, adds functional test for this * fix logic for when to filter out timestamp override documents * update the total hits field of the search result if we find hits within the secondary search. Without updating the total hits field, we could be finding events but not indexing them based on the bulk create logic * update integration test, updates logic for performing second search and excluding documents with timestamp override field * cleanup comments, remove commented out console logs, fix logic to break out of loop during secondary search after * default param to 'succeeded' * remove commented out code * always perform a secondary search when timestamp override field is present * perf improvement and fix bug where sortIds were being mixed between search after calls * set sortIds to undefined when not present on search result * exit loop and prevent extraneous searches from occurring if we exhaust sort ids --- .../signals/build_events_query.test.ts | 6 + .../signals/build_events_query.ts | 94 +++++++------- .../signals/find_threshold_signals.ts | 1 + .../signals/search_after_bulk_create.test.ts | 33 +---- .../signals/search_after_bulk_create.ts | 116 ++++++++++++++---- .../signals/single_search_after.test.ts | 7 +- .../signals/single_search_after.ts | 3 + .../threshold_find_previous_signals.ts | 1 + .../detection_engine/signals/utils.test.ts | 14 ++- .../lib/detection_engine/signals/utils.ts | 67 ++++++++++ .../basic/tests/find_statuses.ts | 4 +- .../basic/tests/open_close_signals.ts | 10 +- .../security_and_spaces/tests/add_actions.ts | 6 +- .../tests/create_exceptions.ts | 8 +- .../security_and_spaces/tests/create_rules.ts | 48 +++++++- .../tests/create_rules_bulk.ts | 4 +- .../tests/create_threat_matching.ts | 12 +- .../exception_operators_data_types/date.ts | 46 +++---- .../exception_operators_data_types/double.ts | 62 +++++----- .../exception_operators_data_types/float.ts | 62 +++++----- .../exception_operators_data_types/integer.ts | 62 +++++----- .../exception_operators_data_types/ip.ts | 58 ++++----- .../ip_array.ts | 56 ++++----- .../exception_operators_data_types/keyword.ts | 48 ++++---- .../keyword_array.ts | 48 ++++---- .../exception_operators_data_types/long.ts | 62 +++++----- .../exception_operators_data_types/text.ts | 74 +++++------ .../text_array.ts | 48 ++++---- .../tests/find_statuses.ts | 4 +- .../tests/generating_signals.ts | 38 +++--- .../tests/open_close_signals.ts | 14 +-- .../detection_engine_api_integration/utils.ts | 30 ++++- .../timestamp_override/data.json.gz | Bin 0 -> 217 bytes .../timestamp_override/mappings.json | 19 +++ 34 files changed, 689 insertions(+), 476 deletions(-) create mode 100644 x-pack/test/functional/es_archives/security_solution/timestamp_override/data.json.gz create mode 100644 x-pack/test/functional/es_archives/security_solution/timestamp_override/mappings.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts index 043066faa8010..f9899fb55bb6a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts @@ -16,6 +16,7 @@ describe('create_signals', () => { size: 100, searchAfterSortId: undefined, timestampOverride: undefined, + excludeDocsWithTimestampOverride: false, }); expect(query).toEqual({ allowNoIndices: true, @@ -95,6 +96,7 @@ describe('create_signals', () => { size: 100, searchAfterSortId: '', timestampOverride: undefined, + excludeDocsWithTimestampOverride: false, }); expect(query).toEqual({ allowNoIndices: true, @@ -175,6 +177,7 @@ describe('create_signals', () => { size: 100, searchAfterSortId: fakeSortId, timestampOverride: undefined, + excludeDocsWithTimestampOverride: false, }); expect(query).toEqual({ allowNoIndices: true, @@ -256,6 +259,7 @@ describe('create_signals', () => { size: 100, searchAfterSortId: fakeSortIdNumber, timestampOverride: undefined, + excludeDocsWithTimestampOverride: false, }); expect(query).toEqual({ allowNoIndices: true, @@ -336,6 +340,7 @@ describe('create_signals', () => { size: 100, searchAfterSortId: undefined, timestampOverride: undefined, + excludeDocsWithTimestampOverride: false, }); expect(query).toEqual({ allowNoIndices: true, @@ -423,6 +428,7 @@ describe('create_signals', () => { size: 100, searchAfterSortId: undefined, timestampOverride: undefined, + excludeDocsWithTimestampOverride: false, }); expect(query).toEqual({ allowNoIndices: true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts index beca56770a9ca..31a424cdbcc1b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts @@ -19,6 +19,7 @@ interface BuildEventsSearchQuery { sortOrder?: SortOrderOrUndefined; searchAfterSortId: string | number | undefined; timestampOverride: TimestampOverrideOrUndefined; + excludeDocsWithTimestampOverride: boolean; } export const buildEventsSearchQuery = ({ @@ -31,66 +32,65 @@ export const buildEventsSearchQuery = ({ searchAfterSortId, sortOrder, timestampOverride, + excludeDocsWithTimestampOverride, }: BuildEventsSearchQuery) => { - const timestamp = timestampOverride ?? '@timestamp'; - const docFields = - timestampOverride != null - ? [ - { - field: '@timestamp', - format: 'strict_date_optional_time', - }, - { - field: timestampOverride, - format: 'strict_date_optional_time', - }, - ] - : [ - { - field: '@timestamp', - format: 'strict_date_optional_time', - }, - ]; + const defaultTimeFields = ['@timestamp']; + const timestamps = + timestampOverride != null ? [timestampOverride, ...defaultTimeFields] : defaultTimeFields; + const docFields = timestamps.map((tstamp) => ({ + field: tstamp, + format: 'strict_date_optional_time', + })); + + const sortField = + timestampOverride != null && !excludeDocsWithTimestampOverride + ? timestampOverride + : '@timestamp'; - const filterWithTime = [ - filter, + const rangeFilter: unknown[] = [ { bool: { - filter: [ + should: [ { - bool: { - should: [ - { - range: { - [timestamp]: { - gte: from, - format: 'strict_date_optional_time', - }, - }, - }, - ], - minimum_should_match: 1, + range: { + [sortField]: { + gte: from, + format: 'strict_date_optional_time', + }, }, }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ { - bool: { - should: [ - { - range: { - [timestamp]: { - lte: to, - format: 'strict_date_optional_time', - }, - }, - }, - ], - minimum_should_match: 1, + range: { + [sortField]: { + lte: to, + format: 'strict_date_optional_time', + }, }, }, ], + minimum_should_match: 1, }, }, ]; + if (excludeDocsWithTimestampOverride) { + rangeFilter.push({ + bool: { + must_not: { + exists: { + field: timestampOverride, + }, + }, + }, + }); + } + const filterWithTime = [filter, { bool: { filter: rangeFilter } }]; const searchQuery = { allowNoIndices: true, @@ -112,7 +112,7 @@ export const buildEventsSearchQuery = ({ ...(aggregations ? { aggregations } : {}), sort: [ { - [timestamp]: { + [sortField]: { order: sortOrder ?? 'asc', }, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts index 7141b61a23e6e..239edcd1f1845 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts @@ -85,5 +85,6 @@ export const findThresholdSignals = async ({ pageSize: 1, sortOrder: 'desc', buildRuleMessage, + excludeDocsWithTimestampOverride: false, }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 67246a830ce90..caac728f0a136 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -183,31 +183,6 @@ describe('searchAfterAndBulkCreate', () => { }, ], }) - .mockResolvedValueOnce(sampleDocSearchResultsNoSortIdNoHits()) - .mockResolvedValueOnce(repeatedSearchResultsWithSortId(4, 1, someGuids.slice(9, 12))) - .mockResolvedValueOnce({ - took: 100, - errors: false, - items: [ - { - create: { - status: 201, - }, - }, - ], - }) - .mockResolvedValueOnce(repeatedSearchResultsWithSortId(4, 1, someGuids.slice(0, 3))) - .mockResolvedValueOnce({ - took: 100, - errors: false, - items: [ - { - create: { - status: 201, - }, - }, - ], - }) .mockResolvedValueOnce(sampleDocSearchResultsNoSortIdNoHits()); const exceptionItem = getExceptionListItemSchemaMock(); @@ -250,8 +225,8 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, }); expect(success).toEqual(true); - expect(mockService.callCluster).toHaveBeenCalledTimes(12); - expect(createdSignalsCount).toEqual(5); + expect(mockService.callCluster).toHaveBeenCalledTimes(8); + expect(createdSignalsCount).toEqual(3); expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); }); @@ -461,7 +436,7 @@ describe('searchAfterAndBulkCreate', () => { // I don't like testing log statements since logs change but this is the best // way I can think of to ensure this section is getting hit with this test case. expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[8][0]).toContain( - 'sortIds was empty on searchResult' + 'ran out of sort ids to sort on name: "fake name" id: "fake id" rule id: "fake rule id" signals index: "fakeindex"' ); }); @@ -542,7 +517,7 @@ describe('searchAfterAndBulkCreate', () => { // I don't like testing log statements since logs change but this is the best // way I can think of to ensure this section is getting hit with this test case. expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[15][0]).toContain( - 'sortIds was empty on searchResult name: "fake name" id: "fake id" rule id: "fake rule id" signals index: "fakeindex"' + 'ran out of sort ids to sort on name: "fake name" id: "fake id" rule id: "fake rule id" signals index: "fakeindex"' ); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index b79f758cd7503..fa47ef25a2db0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable complexity */ import { singleSearchAfter } from './single_search_after'; import { singleBulkCreate } from './single_bulk_create'; @@ -10,10 +11,12 @@ import { filterEventsAgainstList } from './filters/filter_events_against_list'; import { sendAlertTelemetryEvents } from './send_telemetry_events'; import { createSearchAfterReturnType, + createSearchResultReturnType, createSearchAfterReturnTypeFromResponse, createTotalHitsFromSearchResult, getSignalTimeTuples, mergeReturns, + mergeSearchResults, } from './utils'; import { SearchAfterAndBulkCreateParams, SearchAfterAndBulkCreateReturnType } from './types'; @@ -49,6 +52,9 @@ export const searchAfterAndBulkCreate = async ({ // sortId tells us where to start our next consecutive search_after query let sortId: string | undefined; + let hasSortId = true; // default to true so we execute the search on initial run + let backupSortId: string | undefined; + let hasBackupSortId = ruleParams.timestampOverride ? true : false; // signalsCreatedCount keeps track of how many signals we have created, // to ensure we don't exceed maxSignals @@ -78,10 +84,11 @@ export const searchAfterAndBulkCreate = async ({ signalsCreatedCount = 0; while (signalsCreatedCount < tuple.maxSignals) { try { + let mergedSearchResults = createSearchResultReturnType(); logger.debug(buildRuleMessage(`sortIds: ${sortId}`)); // perform search_after with optionally undefined sortId - const { searchResult, searchDuration, searchErrors } = await singleSearchAfter({ + const singleSearchAfterPromise = singleSearchAfter({ buildRuleMessage, searchAfterSortId: sortId, index: inputIndexPattern, @@ -92,23 +99,92 @@ export const searchAfterAndBulkCreate = async ({ filter, pageSize: tuple.maxSignals < pageSize ? Math.ceil(tuple.maxSignals) : pageSize, // maximum number of docs to receive per search result. timestampOverride: ruleParams.timestampOverride, + excludeDocsWithTimestampOverride: false, }); - toReturn = mergeReturns([ - toReturn, - createSearchAfterReturnTypeFromResponse({ - searchResult, + + // if there is a timestampOverride param we always want to do a secondary search against @timestamp + if (ruleParams.timestampOverride != null && hasBackupSortId) { + // only execute search if we have something to sort on or if it is the first search + const singleSearchAfterDefaultTimestamp = singleSearchAfter({ + buildRuleMessage, + searchAfterSortId: backupSortId, + index: inputIndexPattern, + from: tuple.from.toISOString(), + to: tuple.to.toISOString(), + services, + logger, + filter, + pageSize: tuple.maxSignals < pageSize ? Math.ceil(tuple.maxSignals) : pageSize, // maximum number of docs to receive per search result. timestampOverride: ruleParams.timestampOverride, - }), - createSearchAfterReturnType({ - searchAfterTimes: [searchDuration], - errors: searchErrors, - }), - ]); + excludeDocsWithTimestampOverride: true, + }); + const { + searchResult: searchResultB, + searchDuration: searchDurationB, + searchErrors: searchErrorsB, + } = await singleSearchAfterDefaultTimestamp; + + // call this function setSortIdOrExit() + const lastSortId = searchResultB?.hits?.hits[searchResultB.hits.hits.length - 1]?.sort; + if (lastSortId != null && lastSortId.length !== 0) { + backupSortId = lastSortId[0]; + hasBackupSortId = true; + } else { + // if no sort id on backup search and the initial search result was also empty + logger.debug(buildRuleMessage('backupSortIds was empty on searchResultB')); + hasBackupSortId = false; + } + + mergedSearchResults = mergeSearchResults([mergedSearchResults, searchResultB]); + + // merge the search result from the secondary search with the first + toReturn = mergeReturns([ + toReturn, + createSearchAfterReturnTypeFromResponse({ + searchResult: mergedSearchResults, + timestampOverride: undefined, + }), + createSearchAfterReturnType({ + searchAfterTimes: [searchDurationB], + errors: searchErrorsB, + }), + ]); + } + + if (hasSortId) { + // only execute search if we have something to sort on or if it is the first search + const { searchResult, searchDuration, searchErrors } = await singleSearchAfterPromise; + mergedSearchResults = mergeSearchResults([mergedSearchResults, searchResult]); + toReturn = mergeReturns([ + toReturn, + createSearchAfterReturnTypeFromResponse({ + searchResult: mergedSearchResults, + timestampOverride: ruleParams.timestampOverride, + }), + createSearchAfterReturnType({ + searchAfterTimes: [searchDuration], + errors: searchErrors, + }), + ]); + + // we are guaranteed to have searchResult hits at this point + // because we check before if the totalHits or + // searchResult.hits.hits.length is 0 + // call this function setSortIdOrExit() + const lastSortId = searchResult.hits.hits[searchResult.hits.hits.length - 1]?.sort; + if (lastSortId != null && lastSortId.length !== 0) { + sortId = lastSortId[0]; + hasSortId = true; + } else { + hasSortId = false; + } + } + // determine if there are any candidate signals to be processed - const totalHits = createTotalHitsFromSearchResult({ searchResult }); + const totalHits = createTotalHitsFromSearchResult({ searchResult: mergedSearchResults }); logger.debug(buildRuleMessage(`totalHits: ${totalHits}`)); logger.debug( - buildRuleMessage(`searchResult.hit.hits.length: ${searchResult.hits.hits.length}`) + buildRuleMessage(`searchResult.hit.hits.length: ${mergedSearchResults.hits.hits.length}`) ); // search results yielded zero hits so exit @@ -119,7 +195,7 @@ export const searchAfterAndBulkCreate = async ({ // e.g. totalHits was 156, index 50 of 100 results, do another search-after // this time with a new sortId, index 22 of the remaining 56, get another sortId // search with that sortId, total is still 156 but the hits.hits array is empty. - if (totalHits === 0 || searchResult.hits.hits.length === 0) { + if (totalHits === 0 || mergedSearchResults.hits.hits.length === 0) { logger.debug( buildRuleMessage( `${ @@ -137,7 +213,7 @@ export const searchAfterAndBulkCreate = async ({ listClient, exceptionsList, logger, - eventSearchResult: searchResult, + eventSearchResult: mergedSearchResults, buildRuleMessage, }); @@ -205,14 +281,8 @@ export const searchAfterAndBulkCreate = async ({ ); } - // we are guaranteed to have searchResult hits at this point - // because we check before if the totalHits or - // searchResult.hits.hits.length is 0 - const lastSortId = searchResult.hits.hits[searchResult.hits.hits.length - 1].sort; - if (lastSortId != null && lastSortId.length !== 0) { - sortId = lastSortId[0]; - } else { - logger.debug(buildRuleMessage('sortIds was empty on searchResult')); + if (!hasSortId && !hasBackupSortId) { + logger.debug(buildRuleMessage('ran out of sort ids to sort on')); break; } } catch (exc: unknown) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts index c4869f024a977..12d91dcde2ff7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts @@ -40,6 +40,7 @@ describe('singleSearchAfter', () => { filter: undefined, timestampOverride: undefined, buildRuleMessage, + excludeDocsWithTimestampOverride: false, }); expect(searchResult).toEqual(sampleDocSearchResultsNoSortId()); }); @@ -56,6 +57,7 @@ describe('singleSearchAfter', () => { filter: undefined, timestampOverride: undefined, buildRuleMessage, + excludeDocsWithTimestampOverride: false, }); expect(searchErrors).toEqual([]); }); @@ -104,9 +106,10 @@ describe('singleSearchAfter', () => { filter: undefined, timestampOverride: undefined, buildRuleMessage, + excludeDocsWithTimestampOverride: false, }); expect(searchErrors).toEqual([ - 'reason: "some reason" type: "some type" caused by reason: "some reason" caused by type: "some type"', + 'index: "index-123" reason: "some reason" type: "some type" caused by reason: "some reason" caused by type: "some type"', ]); }); test('if singleSearchAfter works with a given sort id', async () => { @@ -123,6 +126,7 @@ describe('singleSearchAfter', () => { filter: undefined, timestampOverride: undefined, buildRuleMessage, + excludeDocsWithTimestampOverride: false, }); expect(searchResult).toEqual(sampleDocSearchResultsWithSortId()); }); @@ -143,6 +147,7 @@ describe('singleSearchAfter', () => { filter: undefined, timestampOverride: undefined, buildRuleMessage, + excludeDocsWithTimestampOverride: false, }) ).rejects.toThrow('Fake Error'); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts index 23ef9fcea8e53..79e1f9896d63f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts @@ -29,6 +29,7 @@ interface SingleSearchAfterParams { filter: unknown; timestampOverride: TimestampOverrideOrUndefined; buildRuleMessage: BuildRuleMessage; + excludeDocsWithTimestampOverride: boolean; } // utilize search_after for paging results into bulk. @@ -45,6 +46,7 @@ export const singleSearchAfter = async ({ sortOrder, timestampOverride, buildRuleMessage, + excludeDocsWithTimestampOverride, }: SingleSearchAfterParams): Promise<{ searchResult: SignalSearchResponse; searchDuration: string; @@ -61,6 +63,7 @@ export const singleSearchAfter = async ({ sortOrder, searchAfterSortId, timestampOverride, + excludeDocsWithTimestampOverride, }); const start = performance.now(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts index 960693bc703d6..6e7f63deb06f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts @@ -83,5 +83,6 @@ export const findPreviousThresholdSignals = async ({ filter, pageSize: 0, buildRuleMessage, + excludeDocsWithTimestampOverride: false, }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index 073e30bbc6e26..b410fb7c35be0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -879,7 +879,7 @@ describe('utils', () => { ]; const createdErrors = createErrorsFromShard({ errors }); expect(createdErrors).toEqual([ - 'reason: "some reason" type: "some type" caused by reason: "some reason" caused by type: "some type"', + 'index: "index-123" reason: "some reason" type: "some type" caused by reason: "some reason" caused by type: "some type"', ]); }); @@ -918,8 +918,8 @@ describe('utils', () => { ]; const createdErrors = createErrorsFromShard({ errors }); expect(createdErrors).toEqual([ - 'reason: "some reason" type: "some type" caused by reason: "some reason" caused by type: "some type"', - 'reason: "some reason 2" type: "some type 2" caused by reason: "some reason 2" caused by type: "some type 2"', + 'index: "index-123" reason: "some reason" type: "some type" caused by reason: "some reason" caused by type: "some type"', + 'index: "index-345" reason: "some reason 2" type: "some type 2" caused by reason: "some reason 2" caused by type: "some type 2"', ]); }); @@ -933,7 +933,7 @@ describe('utils', () => { }, ]; const createdErrors = createErrorsFromShard({ errors }); - expect(createdErrors).toEqual(['']); + expect(createdErrors).toEqual(['index: "index-123"']); }); test('You can have a single value for the shard errors and get expected output without extra spaces anywhere', () => { @@ -948,7 +948,9 @@ describe('utils', () => { }, ]; const createdErrors = createErrorsFromShard({ errors }); - expect(createdErrors).toEqual(['reason: "some reason something went wrong"']); + expect(createdErrors).toEqual([ + 'index: "index-123" reason: "some reason something went wrong"', + ]); }); test('You can have two values for the shard errors and get expected output with one space exactly between the two values', () => { @@ -965,7 +967,7 @@ describe('utils', () => { ]; const createdErrors = createErrorsFromShard({ errors }); expect(createdErrors).toEqual([ - 'reason: "some reason something went wrong" caused by type: "some type"', + 'index: "index-123" reason: "some reason something went wrong" caused by type: "some type"', ]); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 92a27319723d6..ab14643f30e41 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -530,6 +530,7 @@ export const getSignalTimeTuples = ({ export const createErrorsFromShard = ({ errors }: { errors: ShardError[] }): string[] => { return errors.map((error) => { const { + index, reason: { reason, type, @@ -541,6 +542,7 @@ export const createErrorsFromShard = ({ errors }: { errors: ShardError[] }): str } = error; return [ + ...(index != null ? [`index: "${index}"`] : []), ...(reason != null ? [`reason: "${reason}"`] : []), ...(type != null ? [`type: "${type}"`] : []), ...(causedByReason != null ? [`caused by reason: "${causedByReason}"`] : []), @@ -629,6 +631,25 @@ export const createSearchAfterReturnType = ({ }; }; +export const createSearchResultReturnType = (): SignalSearchResponse => { + return { + took: 0, + timed_out: false, + _shards: { + total: 0, + successful: 0, + failed: 0, + skipped: 0, + failures: [], + }, + hits: { + total: 0, + max_score: 0, + hits: [], + }, + }; +}; + export const mergeReturns = ( searchAfters: SearchAfterAndBulkCreateReturnType[] ): SearchAfterAndBulkCreateReturnType => { @@ -665,6 +686,52 @@ export const mergeReturns = ( }); }; +export const mergeSearchResults = (searchResults: SignalSearchResponse[]) => { + return searchResults.reduce((prev, next) => { + const { + took: existingTook, + timed_out: existingTimedOut, + // _scroll_id: existingScrollId, + _shards: existingShards, + // aggregations: existingAggregations, + hits: existingHits, + } = prev; + + const { + took: newTook, + timed_out: newTimedOut, + _scroll_id: newScrollId, + _shards: newShards, + aggregations: newAggregations, + hits: newHits, + } = next; + + return { + took: Math.max(newTook, existingTook), + timed_out: newTimedOut && existingTimedOut, + _scroll_id: newScrollId, + _shards: { + total: newShards.total + existingShards.total, + successful: newShards.successful + existingShards.successful, + failed: newShards.failed + existingShards.failed, + skipped: newShards.skipped + existingShards.skipped, + failures: [ + ...(existingShards.failures != null ? existingShards.failures : []), + ...(newShards.failures != null ? newShards.failures : []), + ], + }, + aggregations: newAggregations, + hits: { + total: + createTotalHitsFromSearchResult({ searchResult: prev }) + + createTotalHitsFromSearchResult({ searchResult: next }), + max_score: Math.max(newHits.max_score, existingHits.max_score), + hits: [...existingHits.hits, ...newHits.hits], + }, + }; + }); +}; + export const createTotalHitsFromSearchResult = ({ searchResult, }: { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts index fe80402b60731..785b74d334276 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts @@ -15,7 +15,7 @@ import { deleteAllRulesStatuses, getSimpleRule, createRule, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -47,7 +47,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return a single rule status when a single rule is loaded from a find status with defaults added', async () => { const resBody = await createRule(supertest, getSimpleRule('rule-1', true)); - await waitForRuleSuccess(supertest, resBody.id); + await waitForRuleSuccessOrStatus(supertest, resBody.id); // query the single rule from _find const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts index f8a25b0081ef9..2e00be6f77061 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts @@ -23,7 +23,7 @@ import { createRule, waitForSignalsToBePresent, getSignalsByIds, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, getRuleForSignalTesting, } from '../../utils'; @@ -79,7 +79,7 @@ export default ({ getService }: FtrProviderContext) => { it('should be able to execute and get 10 signals', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 10, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).equal(10); @@ -88,7 +88,7 @@ export default ({ getService }: FtrProviderContext) => { it('should be have set the signals in an open state initially', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 10, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); const everySignalOpen = signalsOpen.hits.hits.every( @@ -104,7 +104,7 @@ export default ({ getService }: FtrProviderContext) => { it('should be able to get a count of 10 closed signals when closing 10', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 10, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); @@ -131,7 +131,7 @@ export default ({ getService }: FtrProviderContext) => { it('should be able close 10 signals immediately and they all should be closed', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 10, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts index bbd85e353e095..a2c3fc6c6c288 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts @@ -17,7 +17,7 @@ import { getWebHookAction, getRuleWithWebHookAction, getSimpleRuleOutputWithWebHookAction, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, createRule, } from '../../utils'; @@ -60,7 +60,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const rule = await createRule(supertest, getRuleWithWebHookAction(hookAction.id, true)); - await waitForRuleSuccess(supertest, rule.id); + await waitForRuleSuccessOrStatus(supertest, rule.id); // expected result for status should be 'succeeded' const { body } = await supertest @@ -86,7 +86,7 @@ export default ({ getService }: FtrProviderContext) => { }; const rule = await createRule(supertest, ruleWithAction); - await waitForRuleSuccess(supertest, rule.id); + await waitForRuleSuccessOrStatus(supertest, rule.id); // expected result for status should be 'succeeded' const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts index 7e4a6ad86cda5..b90bea66be11f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts @@ -26,7 +26,7 @@ import { removeServerGeneratedProperties, downgradeImmutableRule, createRule, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, installPrePackagedRules, getRule, createExceptionList, @@ -113,7 +113,7 @@ export default ({ getService }: FtrProviderContext) => { }; const rule = await createRule(supertest, ruleWithException); - await waitForRuleSuccess(supertest, rule.id); + await waitForRuleSuccessOrStatus(supertest, rule.id); const bodyToCompare = removeServerGeneratedProperties(rule); const expected: Partial = { @@ -444,7 +444,7 @@ export default ({ getService }: FtrProviderContext) => { ], }; const { id: createdId } = await createRule(supertest, ruleWithException); - await waitForRuleSuccess(supertest, createdId); + await waitForRuleSuccessOrStatus(supertest, createdId); await waitForSignalsToBePresent(supertest, 10, [createdId]); const signalsOpen = await getSignalsByIds(supertest, [createdId]); expect(signalsOpen.hits.hits.length).equal(10); @@ -490,7 +490,7 @@ export default ({ getService }: FtrProviderContext) => { ], }; const rule = await createRule(supertest, ruleWithException); - await waitForRuleSuccess(supertest, rule.id); + await waitForRuleSuccessOrStatus(supertest, rule.id); const signalsOpen = await getSignalsByIds(supertest, [rule.id]); expect(signalsOpen.hits.hits.length).equal(0); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts index 0da12ebba055a..0cde7bf9a22fc 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts @@ -24,13 +24,16 @@ import { removeServerGeneratedPropertiesIncludingRuleId, getSimpleMlRule, getSimpleMlRuleOutput, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, + waitForSignalsToBePresent, getRuleForSignalTesting, + getRuleForSignalTestingWithTimestampOverride, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('create_rules', () => { describe('validation errors', () => { @@ -97,7 +100,7 @@ export default ({ getService }: FtrProviderContext) => { .send(simpleRule) .expect(200); - await waitForRuleSuccess(supertest, body.id); + await waitForRuleSuccessOrStatus(supertest, body.id); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) @@ -201,5 +204,46 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); + describe('missing timestamps', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + // to edit these files run the following script + // cd $HOME/kibana/x-pack && nvm use && node ../scripts/es_archiver edit security_solution/timestamp_override + await esArchiver.load('security_solution/timestamp_override'); + }); + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(supertest); + await esArchiver.unload('security_solution/timestamp_override'); + }); + it('should create a single rule which has a timestamp override and generates two signals with a failing status', async () => { + // should be a failing status because one of the indices in the index pattern is missing + // the timestamp override field. + + // defaults to event.ingested timestamp override. + // event.ingested is one of the timestamp fields set on the es archive data + // inside of x-pack/test/functional/es_archives/security_solution/timestamp_override/data.json.gz + const simpleRule = getRuleForSignalTestingWithTimestampOverride(['myfa*']); + const { body } = await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(simpleRule) + .expect(200); + const bodyId = body.id; + + await waitForRuleSuccessOrStatus(supertest, bodyId, 'failed'); + await waitForSignalsToBePresent(supertest, 2, [bodyId]); + + const { body: statusBody } = await supertest + .post(DETECTION_ENGINE_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .send({ ids: [bodyId] }) + .expect(200); + + // set to "failed" for now. Will update this with a partial failure + // once I figure out the logic + expect(statusBody[bodyId].current_status.status).to.eql('failed'); + }); + }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts index 7ea47312a5030..2577c6b163604 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts @@ -22,7 +22,7 @@ import { getSimpleRuleWithoutRuleId, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -99,7 +99,7 @@ export default ({ getService }: FtrProviderContext): void => { .send([simpleRule]) .expect(200); - await waitForRuleSuccess(supertest, body[0].id); + await waitForRuleSuccessOrStatus(supertest, body[0].id); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts index 21cfab3db6d6a..1f7deddbd5e76 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts @@ -19,7 +19,7 @@ import { deleteSignalsIndex, getSignalsByIds, removeServerGeneratedProperties, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../utils'; @@ -72,7 +72,7 @@ export default ({ getService }: FtrProviderContext) => { supertest, getCreateThreatMatchRulesSchemaMock('rule-1', true) ); - await waitForRuleSuccess(supertest, ruleResponse.id); + await waitForRuleSuccessOrStatus(supertest, ruleResponse.id); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) @@ -128,7 +128,7 @@ export default ({ getService }: FtrProviderContext) => { }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 10, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).equal(10); @@ -163,7 +163,7 @@ export default ({ getService }: FtrProviderContext) => { }; const ruleResponse = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, ruleResponse.id); + await waitForRuleSuccessOrStatus(supertest, ruleResponse.id); const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -201,7 +201,7 @@ export default ({ getService }: FtrProviderContext) => { }; const ruleResponse = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, ruleResponse.id); + await waitForRuleSuccessOrStatus(supertest, ruleResponse.id); const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); @@ -239,7 +239,7 @@ export default ({ getService }: FtrProviderContext) => { }; const ruleResponse = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, ruleResponse.id); + await waitForRuleSuccessOrStatus(supertest, ruleResponse.id); const signalsOpen = await getSignalsByIds(supertest, [ruleResponse.id]); expect(signalsOpen.hits.hits.length).equal(0); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts index 09cc470defa08..4271ce9b37ebb 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/date.ts @@ -21,7 +21,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -50,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the dates from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['date']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -74,7 +74,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -105,7 +105,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -140,7 +140,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -183,7 +183,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); expect(hits).to.eql([]); @@ -203,7 +203,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); expect(hits).to.eql([]); @@ -221,7 +221,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -248,7 +248,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); expect(hits).to.eql([]); @@ -268,7 +268,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -291,7 +291,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -314,7 +314,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -338,7 +338,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); expect(hits).to.eql([]); @@ -358,7 +358,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); expect(hits).to.eql([]); @@ -376,7 +376,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -396,7 +396,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); expect(hits).to.eql([]); @@ -415,7 +415,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -445,7 +445,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -477,7 +477,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -510,7 +510,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); expect(hits).to.eql([]); @@ -534,7 +534,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -562,7 +562,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); @@ -595,7 +595,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.date).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts index a5793489cd8d0..158e17299fe9f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/double.ts @@ -21,7 +21,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -52,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the double from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['double']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -71,7 +71,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -98,7 +98,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -133,7 +133,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -176,7 +176,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); expect(hits).to.eql([]); @@ -196,7 +196,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); expect(hits).to.eql([]); @@ -214,7 +214,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -241,7 +241,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); expect(hits).to.eql([]); @@ -261,7 +261,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -280,7 +280,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -299,7 +299,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -318,7 +318,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); expect(hits).to.eql([]); @@ -338,7 +338,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); expect(hits).to.eql([]); @@ -356,7 +356,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -376,7 +376,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); expect(hits).to.eql([]); @@ -395,7 +395,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -421,7 +421,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -444,7 +444,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -467,7 +467,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); expect(hits).to.eql([]); @@ -491,7 +491,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -514,7 +514,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -537,7 +537,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); expect(hits).to.eql([]); @@ -562,7 +562,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -589,7 +589,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -612,7 +612,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -635,7 +635,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -660,7 +660,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -683,7 +683,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -706,7 +706,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); @@ -732,7 +732,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.double).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts index 955d27c086466..0bea2d73151f2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/float.ts @@ -21,7 +21,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -52,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the float from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['float']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -71,7 +71,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -98,7 +98,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -133,7 +133,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -176,7 +176,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); expect(hits).to.eql([]); @@ -196,7 +196,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); expect(hits).to.eql([]); @@ -214,7 +214,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -241,7 +241,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); expect(hits).to.eql([]); @@ -261,7 +261,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -280,7 +280,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -299,7 +299,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -318,7 +318,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); expect(hits).to.eql([]); @@ -338,7 +338,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); expect(hits).to.eql([]); @@ -356,7 +356,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -376,7 +376,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); expect(hits).to.eql([]); @@ -395,7 +395,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -421,7 +421,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -444,7 +444,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -467,7 +467,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); expect(hits).to.eql([]); @@ -491,7 +491,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -514,7 +514,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -537,7 +537,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); expect(hits).to.eql([]); @@ -559,7 +559,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -586,7 +586,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -609,7 +609,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -632,7 +632,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -657,7 +657,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -680,7 +680,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -703,7 +703,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); @@ -726,7 +726,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.float).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts index a1275afe288bf..600c1a609a694 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/integer.ts @@ -21,7 +21,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -52,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the integer from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['integer']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -71,7 +71,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -98,7 +98,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -133,7 +133,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -176,7 +176,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); expect(hits).to.eql([]); @@ -196,7 +196,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); expect(hits).to.eql([]); @@ -214,7 +214,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -241,7 +241,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); expect(hits).to.eql([]); @@ -261,7 +261,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -280,7 +280,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -299,7 +299,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -318,7 +318,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); expect(hits).to.eql([]); @@ -338,7 +338,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); expect(hits).to.eql([]); @@ -356,7 +356,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -376,7 +376,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); expect(hits).to.eql([]); @@ -395,7 +395,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -421,7 +421,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -444,7 +444,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -467,7 +467,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); expect(hits).to.eql([]); @@ -491,7 +491,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -514,7 +514,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -537,7 +537,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); expect(hits).to.eql([]); @@ -559,7 +559,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -586,7 +586,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -609,7 +609,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -632,7 +632,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -657,7 +657,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -680,7 +680,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -703,7 +703,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); @@ -726,7 +726,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.integer).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts index 311354c63ca4a..bcdebed3dd45b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip.ts @@ -21,7 +21,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -50,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the ips from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['ip']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -69,7 +69,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -96,7 +96,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -131,7 +131,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -174,7 +174,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([]); @@ -192,7 +192,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -213,7 +213,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([]); @@ -231,7 +231,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -258,7 +258,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([]); @@ -278,7 +278,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -297,7 +297,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -316,7 +316,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -335,7 +335,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([]); @@ -355,7 +355,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([]); @@ -373,7 +373,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -393,7 +393,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([]); @@ -412,7 +412,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -437,7 +437,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -460,7 +460,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -488,7 +488,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([]); @@ -514,7 +514,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -541,7 +541,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -570,7 +570,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -595,7 +595,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -618,7 +618,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -646,7 +646,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -673,7 +673,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -700,7 +700,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip_array.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip_array.ts index 8f4827ec6e71c..9d6f1f2fb297a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip_array.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/ip_array.ts @@ -21,7 +21,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -50,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the ips from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['ip_as_array']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -74,7 +74,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -105,7 +105,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -140,7 +140,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -159,7 +159,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -182,7 +182,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -203,7 +203,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([]); @@ -221,7 +221,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -246,7 +246,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -273,7 +273,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([]); @@ -293,7 +293,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -316,7 +316,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -335,7 +335,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -356,7 +356,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([]); @@ -374,7 +374,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -397,7 +397,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([[]]); @@ -416,7 +416,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -445,7 +445,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -472,7 +472,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -500,7 +500,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); expect(ips).to.eql([[]]); @@ -536,7 +536,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -567,7 +567,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -592,7 +592,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -615,7 +615,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -646,7 +646,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -687,7 +687,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); @@ -721,7 +721,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const ips = signalsOpen.hits.hits.map((hit) => hit._source.ip).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts index e4e80cb1b65ea..a0183ad794a2f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword.ts @@ -21,7 +21,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -50,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the keyword from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['keyword']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -69,7 +69,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -96,7 +96,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -131,7 +131,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -174,7 +174,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([]); @@ -194,7 +194,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([]); @@ -212,7 +212,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -239,7 +239,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([]); @@ -259,7 +259,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -278,7 +278,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -297,7 +297,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -316,7 +316,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([]); @@ -336,7 +336,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([]); @@ -354,7 +354,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -374,7 +374,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([]); @@ -393,7 +393,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -428,7 +428,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -451,7 +451,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -474,7 +474,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -502,7 +502,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([]); @@ -526,7 +526,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -549,7 +549,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -577,7 +577,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword_array.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword_array.ts index 01e301c350851..81ea04de5def0 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword_array.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/keyword_array.ts @@ -21,7 +21,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -50,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the keyword from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['keyword_as_array']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -74,7 +74,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -105,7 +105,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -140,7 +140,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -161,7 +161,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([]); @@ -179,7 +179,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -206,7 +206,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([]); @@ -226,7 +226,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -249,7 +249,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -268,7 +268,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -289,7 +289,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([]); @@ -307,7 +307,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -330,7 +330,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([[]]); @@ -349,7 +349,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -388,7 +388,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -426,7 +426,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -453,7 +453,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -480,7 +480,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -508,7 +508,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); expect(hits).to.eql([[]]); @@ -532,7 +532,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -555,7 +555,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -578,7 +578,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); @@ -609,7 +609,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.keyword).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts index ee52c41bc78e8..56667dbca925e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/long.ts @@ -21,7 +21,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -52,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the long from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['long']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -71,7 +71,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -98,7 +98,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -133,7 +133,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -176,7 +176,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); expect(hits).to.eql([]); @@ -196,7 +196,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); expect(hits).to.eql([]); @@ -214,7 +214,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -241,7 +241,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); expect(hits).to.eql([]); @@ -261,7 +261,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -280,7 +280,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -299,7 +299,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -318,7 +318,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); expect(hits).to.eql([]); @@ -338,7 +338,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); expect(hits).to.eql([]); @@ -356,7 +356,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -376,7 +376,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); expect(hits).to.eql([]); @@ -395,7 +395,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -421,7 +421,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -444,7 +444,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -467,7 +467,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); expect(hits).to.eql([]); @@ -491,7 +491,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -514,7 +514,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -537,7 +537,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); expect(hits).to.eql([]); @@ -559,7 +559,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -586,7 +586,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -609,7 +609,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -632,7 +632,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -657,7 +657,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -680,7 +680,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -703,7 +703,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); @@ -726,7 +726,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.long).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts index 095d885149389..74507fc030e68 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text.ts @@ -22,7 +22,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -53,7 +53,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the text from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['text']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -72,7 +72,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -99,7 +99,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -134,7 +134,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -177,7 +177,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -195,7 +195,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -214,7 +214,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -232,7 +232,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -253,7 +253,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -271,7 +271,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -298,7 +298,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -316,7 +316,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -335,7 +335,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql(['word four', 'word one', 'word three', 'word two']); @@ -353,7 +353,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -374,7 +374,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -393,7 +393,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -412,7 +412,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -431,7 +431,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -451,7 +451,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -469,7 +469,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -489,7 +489,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -508,7 +508,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -534,7 +534,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -557,7 +557,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -585,7 +585,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -609,7 +609,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -637,7 +637,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -660,7 +660,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -688,7 +688,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -714,7 +714,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -737,7 +737,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -765,7 +765,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -790,7 +790,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -818,7 +818,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -841,7 +841,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -869,7 +869,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text_array.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text_array.ts index ed63f1a0db25f..9a77cee6be1eb 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text_array.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/text_array.ts @@ -21,7 +21,7 @@ import { deleteSignalsIndex, getRuleForSignalTesting, getSignalsById, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../../utils'; @@ -50,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => { it('should find all the text from the data set when no exceptions are set on the rule', async () => { const rule = getRuleForSignalTesting(['text_as_array']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -74,7 +74,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -105,7 +105,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -140,7 +140,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -161,7 +161,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -179,7 +179,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -206,7 +206,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -226,7 +226,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -249,7 +249,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -268,7 +268,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -289,7 +289,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([]); @@ -307,7 +307,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -330,7 +330,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([[]]); @@ -349,7 +349,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -388,7 +388,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -426,7 +426,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -453,7 +453,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -480,7 +480,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -508,7 +508,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); expect(hits).to.eql([[]]); @@ -532,7 +532,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -555,7 +555,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -578,7 +578,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 2, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); @@ -604,7 +604,7 @@ export default ({ getService }: FtrProviderContext) => { }, ], ]); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 3, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source.text).sort(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts index 8bb4c45d91bdd..dfec35e4a64f3 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts @@ -14,7 +14,7 @@ import { deleteSignalsIndex, deleteAllRulesStatuses, getSimpleRule, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, createRule, } from '../../utils'; @@ -66,7 +66,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return a single rule status when a single rule is loaded from a find status with defaults added', async () => { const resBody = await createRule(supertest, getSimpleRule('rule-1', true)); - await waitForRuleSuccess(supertest, resBody.id); + await waitForRuleSuccessOrStatus(supertest, resBody.id); // query the single rule from _find const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index e3264786ff38b..34f7074326550 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -22,7 +22,7 @@ import { getSignalsByIds, getSignalsByRuleIds, getSimpleRule, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, waitForSignalsToBePresent, } from '../../utils'; import { SIGNALS_TEMPLATE_VERSION } from '../../../../plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template'; @@ -63,7 +63,7 @@ export default ({ getService }: FtrProviderContext) => { query: `_id:${ID}`, }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).greaterThan(0); @@ -75,7 +75,7 @@ export default ({ getService }: FtrProviderContext) => { query: `_id:${ID}`, }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); @@ -87,7 +87,7 @@ export default ({ getService }: FtrProviderContext) => { query: `_id:${ID}`, }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); // remove rule to cut down on touch points for test changes when the rule format changes @@ -136,7 +136,7 @@ export default ({ getService }: FtrProviderContext) => { query: `_id:${ID}`, }; const { id: createdId } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, createdId); + await waitForRuleSuccessOrStatus(supertest, createdId); await waitForSignalsToBePresent(supertest, 1, [createdId]); // Run signals on top of that 1 signal which should create a single signal (on top of) a signal @@ -146,7 +146,7 @@ export default ({ getService }: FtrProviderContext) => { }; const { id } = await createRule(supertest, ruleForSignals); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); // Get our single signal on top of a signal @@ -212,7 +212,7 @@ export default ({ getService }: FtrProviderContext) => { query: 'sequence by host.name [any where true] [any where true]', }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signals = await getSignalsByRuleIds(supertest, ['eql-rule']); const signal = signals.hits.hits[0]._source.signal; @@ -267,7 +267,7 @@ export default ({ getService }: FtrProviderContext) => { query: 'sequence by host.name [any where true] [any where true]', }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByRuleIds(supertest, ['eql-rule']); const sequenceSignal = signalsOpen.hits.hits.find( @@ -355,7 +355,7 @@ export default ({ getService }: FtrProviderContext) => { }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).greaterThan(0); @@ -368,7 +368,7 @@ export default ({ getService }: FtrProviderContext) => { }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); @@ -380,7 +380,7 @@ export default ({ getService }: FtrProviderContext) => { query: '_id:1', }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); // remove rule to cut down on touch points for test changes when the rule format changes @@ -424,7 +424,7 @@ export default ({ getService }: FtrProviderContext) => { query: '_id:1', }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); // Run signals on top of that 1 signal which should create a single signal (on top of) a signal @@ -433,7 +433,7 @@ export default ({ getService }: FtrProviderContext) => { rule_id: 'signal-on-signal', }; const { id: createdId } = await createRule(supertest, ruleForSignals); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [createdId]); // Get our single signal on top of a signal @@ -508,7 +508,7 @@ export default ({ getService }: FtrProviderContext) => { query: '_id:1', }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).greaterThan(0); @@ -520,7 +520,7 @@ export default ({ getService }: FtrProviderContext) => { query: '_id:1', }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); @@ -532,7 +532,7 @@ export default ({ getService }: FtrProviderContext) => { query: '_id:1', }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); // remove rule to cut down on touch points for test changes when the rule format changes @@ -582,7 +582,7 @@ export default ({ getService }: FtrProviderContext) => { query: '_id:1', }; const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); // Run signals on top of that 1 signal which should create a single signal (on top of) a signal @@ -591,7 +591,7 @@ export default ({ getService }: FtrProviderContext) => { rule_id: 'signal-on-signal', }; const { id: createdId } = await createRule(supertest, ruleForSignals); - await waitForRuleSuccess(supertest, createdId); + await waitForRuleSuccessOrStatus(supertest, createdId); await waitForSignalsToBePresent(supertest, 1, [createdId]); // Get our single signal on top of a signal @@ -661,7 +661,7 @@ export default ({ getService }: FtrProviderContext) => { const executeRuleAndGetSignals = async (rule: QueryCreateSchema) => { const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsResponse = await getSignalsByIds(supertest, [id]); const signals = signalsResponse.hits.hits.map((hit) => hit._source); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts index 87e3b145ed6fd..ee787f1b616e3 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts @@ -23,7 +23,7 @@ import { createRule, waitForSignalsToBePresent, getSignalsByIds, - waitForRuleSuccess, + waitForRuleSuccessOrStatus, getRuleForSignalTesting, } from '../../utils'; import { createUserAndRole } from '../roles_users_utils'; @@ -82,7 +82,7 @@ export default ({ getService }: FtrProviderContext) => { it('should be able to execute and get 10 signals', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 10, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); expect(signalsOpen.hits.hits.length).equal(10); @@ -91,7 +91,7 @@ export default ({ getService }: FtrProviderContext) => { it('should be have set the signals in an open state initially', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 10, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); const everySignalOpen = signalsOpen.hits.hits.every( @@ -107,7 +107,7 @@ export default ({ getService }: FtrProviderContext) => { it('should be able to get a count of 10 closed signals when closing 10', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 10, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); @@ -134,7 +134,7 @@ export default ({ getService }: FtrProviderContext) => { it('should be able close signals immediately and they all should be closed', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const signalsOpen = await getSignalsByIds(supertest, [id]); const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); @@ -169,7 +169,7 @@ export default ({ getService }: FtrProviderContext) => { it('should NOT be able to close signals with t1 analyst user', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); await createUserAndRole(securityService, ROLES.t1_analyst); const signalsOpen = await getSignalsByIds(supertest, [id]); @@ -207,7 +207,7 @@ export default ({ getService }: FtrProviderContext) => { it('should be able to close signals with soc_manager user', async () => { const rule = getRuleForSignalTesting(['auditbeat-*']); const { id } = await createRule(supertest, rule); - await waitForRuleSuccess(supertest, id); + await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 1, [id]); const userAndRole = ROLES.soc_manager; await createUserAndRole(securityService, userAndRole); diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 5a36b950b6a5b..9cff40758bd49 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -119,6 +119,25 @@ export const getRuleForSignalTesting = ( from: '1900-01-01T00:00:00.000Z', }); +export const getRuleForSignalTestingWithTimestampOverride = ( + index: string[], + ruleId = 'rule-1', + enabled = true, + timestampOverride = 'event.ingested' +): QueryCreateSchema => ({ + name: 'Signal Testing Query', + description: 'Tests a simple query', + enabled, + risk_score: 1, + rule_id: ruleId, + severity: 'high', + index, + type: 'query', + query: '*:*', + timestamp_override: timestampOverride, + from: '1900-01-01T00:00:00.000Z', +}); + /** * This is a typical simple rule for testing that is easy for most basic testing * @param ruleId The rule id @@ -864,21 +883,22 @@ export const getRule = async ( }; /** - * Waits for the rule in find status to be succeeded before continuing + * Waits for the rule in find status to be 'succeeded' + * or the provided status, before continuing * @param supertest Deps */ -export const waitForRuleSuccess = async ( +export const waitForRuleSuccessOrStatus = async ( supertest: SuperTest, - id: string + id: string, + status: 'succeeded' | 'failed' | 'partial failure' = 'succeeded' ): Promise => { - // wait for Task Manager to finish executing the rule await waitFor(async () => { const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) .set('kbn-xsrf', 'true') .send({ ids: [id] }) .expect(200); - return body[id]?.current_status?.status === 'succeeded'; + return body[id]?.current_status?.status === status; }, 'waitForRuleSuccess'); }; diff --git a/x-pack/test/functional/es_archives/security_solution/timestamp_override/data.json.gz b/x-pack/test/functional/es_archives/security_solution/timestamp_override/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..be351495c2f2e11b896d0b657c6285d0713cd418 GIT binary patch literal 217 zcmV;~04Dz*iwFP!000026Qz&M4uUWcgztTdre`s<1T>s{2v1%Nffb2h2^5r&zPoMZ zM^vKGTX*)G?o5j?CZfrK_?SdIBnvInL0W00Rf8Ina|BlnWX&Nsff+4oP_-?2RfHq0 zlnlx;h|QNrNK=k4yhtNVi2-Ei>#y$hStUs%5o)KqKG932xm2vf-{qQ5Ho6o8HJru7 zW{$;B3W&6m+03>mv#7VFu1`Imu9xo0-jTr|yO$ioJeR#QsxZd?R(5`>>^xT9h(uu` T$ntOceQb3Dk3QF;RRRD2u~uS3 literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/security_solution/timestamp_override/mappings.json b/x-pack/test/functional/es_archives/security_solution/timestamp_override/mappings.json new file mode 100644 index 0000000000000..28de7eeb2eb01 --- /dev/null +++ b/x-pack/test/functional/es_archives/security_solution/timestamp_override/mappings.json @@ -0,0 +1,19 @@ +{ + "type": "index", + "value": { + "index": "myfakeindex-1", + "mappings" : { + "properties" : { + "message" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + } + } + } + } +} \ No newline at end of file From 25bf06c57358e7392777ae33952339aea4af4ad1 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 24 Dec 2020 11:01:01 +0300 Subject: [PATCH 50/72] Add telemetry to vis_type_xy plugin (#86751) * Add telemetry for detailedTooltip and fittingFunction * Fixed type problems Co-authored-by: nickofthyme Co-authored-by: Ryan Keairns Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: Joe Reuter Co-authored-by: Stratoula Kalafateli --- src/plugins/vis_type_xy/kibana.json | 2 +- .../point_series/elastic_charts_options.tsx | 17 +++++++++++++++-- src/plugins/vis_type_xy/public/plugin.ts | 7 ++++++- src/plugins/vis_type_xy/public/services.ts | 5 +++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/plugins/vis_type_xy/kibana.json b/src/plugins/vis_type_xy/kibana.json index 14c3ce36bf375..619fa8e71c0dd 100644 --- a/src/plugins/vis_type_xy/kibana.json +++ b/src/plugins/vis_type_xy/kibana.json @@ -3,6 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations"], + "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection"], "requiredBundles": ["kibanaUtils", "visDefaultEditor"] } diff --git a/src/plugins/vis_type_xy/public/editor/components/options/point_series/elastic_charts_options.tsx b/src/plugins/vis_type_xy/public/editor/components/options/point_series/elastic_charts_options.tsx index f40972e86af6b..a3e573741644c 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/point_series/elastic_charts_options.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/point_series/elastic_charts_options.tsx @@ -20,14 +20,17 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; +import { METRIC_TYPE } from '@kbn/analytics'; import { SelectOption, SwitchOption } from '../../../../../../vis_default_editor/public'; import { ChartType } from '../../../../../common'; import { VisParams } from '../../../../types'; import { ValidationVisOptionsProps } from '../../common'; +import { getTrackUiMetric } from '../../../../services'; export function ElasticChartsOptions(props: ValidationVisOptionsProps) { + const trackUiMetric = getTrackUiMetric(); const { stateParams, setValue, vis, aggs } = props; const hasLineChart = stateParams.seriesParams.some( @@ -49,7 +52,12 @@ export function ElasticChartsOptions(props: ValidationVisOptionsProps })} paramName="detailedTooltip" value={stateParams.detailedTooltip} - setValue={setValue} + setValue={(paramName, value) => { + if (trackUiMetric) { + trackUiMetric(METRIC_TYPE.CLICK, 'detailed_tooltip_switched'); + } + setValue(paramName, value); + }} /> {hasLineChart && ( @@ -61,7 +69,12 @@ export function ElasticChartsOptions(props: ValidationVisOptionsProps options={vis.type.editorConfig.collections.fittingFunctions} paramName="fittingFunction" value={stateParams.fittingFunction} - setValue={setValue} + setValue={(paramName, value) => { + if (trackUiMetric) { + trackUiMetric(METRIC_TYPE.CLICK, 'fitting_function_selected'); + } + setValue(paramName, value); + }} /> )} diff --git a/src/plugins/vis_type_xy/public/plugin.ts b/src/plugins/vis_type_xy/public/plugin.ts index 07084de67fd21..ab22ae57ebbdf 100644 --- a/src/plugins/vis_type_xy/public/plugin.ts +++ b/src/plugins/vis_type_xy/public/plugin.ts @@ -22,6 +22,7 @@ import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; import { ChartsPluginSetup } from '../../charts/public'; import { DataPublicPluginStart } from '../../data/public'; +import { UsageCollectionSetup } from '../../usage_collection/public'; import { createVisTypeXyVisFn } from './xy_vis_fn'; import { @@ -32,6 +33,7 @@ import { setTimefilter, setUISettings, setDocLinks, + setTrackUiMetric, } from './services'; import { visTypesDefinitions } from './vis_types'; import { LEGACY_CHARTS_LIBRARY } from '../common'; @@ -47,6 +49,7 @@ export interface VisTypeXyPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; charts: ChartsPluginSetup; + usageCollection: UsageCollectionSetup; } /** @internal */ @@ -69,7 +72,7 @@ export class VisTypeXyPlugin > { public async setup( core: VisTypeXyCoreSetup, - { expressions, visualizations, charts }: VisTypeXyPluginSetupDependencies + { expressions, visualizations, charts, usageCollection }: VisTypeXyPluginSetupDependencies ) { if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, false)) { setUISettings(core.uiSettings); @@ -81,6 +84,8 @@ export class VisTypeXyPlugin visTypesDefinitions.forEach(visualizations.createBaseVisualization); } + setTrackUiMetric(usageCollection?.reportUiCounter.bind(usageCollection, 'vis_type_xy')); + return {}; } diff --git a/src/plugins/vis_type_xy/public/services.ts b/src/plugins/vis_type_xy/public/services.ts index 5a72759ecff6c..086cab8fb217a 100644 --- a/src/plugins/vis_type_xy/public/services.ts +++ b/src/plugins/vis_type_xy/public/services.ts @@ -17,6 +17,7 @@ * under the License. */ +import { UiCounterMetricType } from '@kbn/analytics'; import { CoreSetup, DocLinksStart } from '../../../core/public'; import { createGetterSetter } from '../../kibana_utils/public'; import { DataPublicPluginStart } from '../../data/public'; @@ -47,3 +48,7 @@ export const [getColorsService, setColorsService] = createGetterSetter< >('xy charts.color'); export const [getDocLinks, setDocLinks] = createGetterSetter('DocLinks'); + +export const [getTrackUiMetric, setTrackUiMetric] = createGetterSetter< + (metricType: UiCounterMetricType, eventName: string | string[]) => void +>('trackUiMetric'); From 6b257bb6c3e395b93d6b8645899d4e99adbdb709 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Dec 2020 10:01:56 +0200 Subject: [PATCH 51/72] Update dependency vega to ^5.17.3 (#86891) Co-authored-by: Renovate Bot --- package.json | 2 +- yarn.lock | 76 ++++++++++++++++++++++++++-------------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 49787bb8ab34e..61b13a06bffe9 100644 --- a/package.json +++ b/package.json @@ -824,7 +824,7 @@ "url-loader": "^2.2.0", "use-resize-observer": "^6.0.0", "val-loader": "^1.1.1", - "vega": "^5.17.1", + "vega": "^5.17.3", "vega-lite": "^4.17.0", "vega-schema-url-parser": "^2.1.0", "vega-tooltip": "^0.24.2", diff --git a/yarn.lock b/yarn.lock index 6fa3ea09731b3..956630bafa935 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28340,20 +28340,20 @@ vega-event-selector@^2.0.6, vega-event-selector@~2.0.6: resolved "https://registry.yarnpkg.com/vega-event-selector/-/vega-event-selector-2.0.6.tgz#6beb00e066b78371dde1a0f40cb5e0bbaecfd8bc" integrity sha512-UwCu50Sqd8kNZ1X/XgiAY+QAyQUmGFAwyDu7y0T5fs6/TPQnDo/Bo346NgSgINBEhEKOAMY1Nd/rPOk4UEm/ew== -vega-expression@^3.0.0, vega-expression@~3.0.0: +vega-expression@^4.0.0, vega-expression@^4.0.1, vega-expression@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-4.0.1.tgz#c03e4fc68a00acac49557faa4e4ed6ac8a59c5fd" + integrity sha512-ZrDj0hP8NmrCpdLFf7Rd/xMUHGoSYsAOTaYp7uXZ2dkEH5x0uPy5laECMc8TiQvL8W+8IrN2HAWCMRthTSRe2Q== + dependencies: + vega-util "^1.16.0" + +vega-expression@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-3.0.0.tgz#39179d010b34c57513162bf1ab5a7bff4b31be91" integrity sha512-/ObjIOK94MB+ziTuh8HZt2eWlKUPT/piRJLal5tx5QL1sQbfRi++7lHKTaKMLXLqc4Xqp9/DewE3PqQ6tYzaUA== dependencies: vega-util "^1.15.2" -vega-expression@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-3.0.1.tgz#bbccd8f59371a537eab16f3d9eff5cbeaa27532d" - integrity sha512-+UwOFEkBnAWo8Zud6i8O4Pd2W6QqmPUOaAhjNtj0OxRL+d+Duoy7M4edUDZ+YuoUcMnjjBFfDQu7oRAA1fIMEQ== - dependencies: - vega-util "^1.15.2" - vega-force@~4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/vega-force/-/vega-force-4.0.7.tgz#6dc39ecb7889d9102661244d62fbc8d8714162ee" @@ -28374,22 +28374,22 @@ vega-format@^1.0.4, vega-format@~1.0.4: vega-time "^2.0.3" vega-util "^1.15.2" -vega-functions@^5.8.0, vega-functions@~5.8.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.8.0.tgz#48e02b0e5b14261cd445bda3c4721a18b02c810c" - integrity sha512-xaUqWZHEX+EuJuKfN0Biux3rrCHDEHmMbW7LHYyyEqguR0i6+zhtOSUEWmYqDfzB/+BlIwCk5Vif6q6/mzJxbQ== +vega-functions@^5.10.0, vega-functions@~5.10.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.10.0.tgz#3d384111f13b3b0dd38a4fca656c5ae54b66e158" + integrity sha512-1l28OxUwOj8FEvRU62Oz2hiTuDECrvx1DPU1qLebBKhlgaKbcCk3XyHrn1kUzhMKpXq+SFv5VPxchZP47ASSvQ== dependencies: d3-array "^2.7.1" d3-color "^2.0.0" d3-geo "^2.0.1" vega-dataflow "^5.7.3" - vega-expression "^3.0.0" + vega-expression "^4.0.1" vega-scale "^7.1.1" vega-scenegraph "^4.9.2" - vega-selections "^5.1.4" + vega-selections "^5.1.5" vega-statistics "^1.7.9" vega-time "^2.0.4" - vega-util "^1.15.2" + vega-util "^1.16.0" vega-geo@~4.3.8: version "4.3.8" @@ -28453,14 +28453,14 @@ vega-loader@^4.3.2, vega-loader@^4.3.3, vega-loader@~4.4.0: vega-format "^1.0.4" vega-util "^1.16.0" -vega-parser@~6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.1.0.tgz#485fb6fcd79d14b09efee340e2b55fb510e57e20" - integrity sha512-u14bHXV8vtcuMIJkMNoDAJ4Xu3lwKIkep+YEkPumWvlwl3fClWy26EAcwTneeM3rXu2F6ZJI6W3ddu/If8u13w== +vega-parser@~6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.1.2.tgz#7f25751177e38c3239560a9c427ded8d2ba617bb" + integrity sha512-aGyZrNzPrBruEb/WhemKDuDjQsIkMDGIgnSJci0b+9ZVxjyAzMl7UfGbiYorPiJlnIercjUJbMoFD6fCIf4gqQ== dependencies: vega-dataflow "^5.7.3" vega-event-selector "^2.0.6" - vega-functions "^5.8.0" + vega-functions "^5.10.0" vega-scale "^7.1.1" vega-util "^1.15.2" @@ -28518,12 +28518,12 @@ vega-schema-url-parser@^2.1.0: resolved "https://registry.yarnpkg.com/vega-schema-url-parser/-/vega-schema-url-parser-2.1.0.tgz#847f9cf9f1624f36f8a51abc1adb41ebc6673cb4" integrity sha512-JHT1PfOyVzOohj89uNunLPirs05Nf59isPT5gnwIkJph96rRgTIBJE7l7yLqndd7fLjr3P8JXHGAryRp74sCaQ== -vega-selections@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/vega-selections/-/vega-selections-5.1.4.tgz#cc086fac5b4e646f9f1e000777f8786782d8516a" - integrity sha512-L7CHwcIjVf90GoW2tS2x5O496O5Joaerp5A1KM6VJ1uo4z6KfqxY6M/328a/uaAs0LC5qbQgXT3htFbtUrPW/A== +vega-selections@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/vega-selections/-/vega-selections-5.1.5.tgz#c7662edf26c1cfb18623573b30590c9774348d1c" + integrity sha512-oRSsfkqYqA5xfEJqDpgnSDd+w0k6p6SGYisMD6rGXMxuPl0x0Uy6RvDr4nbEtB+dpWdoWEvgrsZVS6axyDNWvQ== dependencies: - vega-expression "^3.0.0" + vega-expression "^4.0.0" vega-util "^1.15.2" vega-spec-injector@^0.0.2: @@ -28586,16 +28586,16 @@ vega-view-transforms@~4.5.8: vega-scenegraph "^4.9.2" vega-util "^1.15.2" -vega-view@~5.9.0: - version "5.9.0" - resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.9.0.tgz#ee6d5abd66d2503dec71e05e7ca8cf813465ae3f" - integrity sha512-HqRFuqO2OwoPHHK+CVt8vB8fu2L8GjQerLpmEpglWtCPDns5+gn5B6F7M8Ah8v24WlfqW7cLrY81t9OloPZOyw== +vega-view@~5.9.2: + version "5.9.2" + resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.9.2.tgz#cb957e481a952abbe7b3a11aa2d58cc728f295e7" + integrity sha512-XAwKWyVjLClR3aCbTLCWdZj7aZozOULNg7078GxJIgVcBJOENCAidceI/H7JieyUZ96p3AiEHLQdWr167InBpg== dependencies: d3-array "^2.7.1" d3-timer "^2.0.0" vega-dataflow "^5.7.3" vega-format "^1.0.4" - vega-functions "^5.8.0" + vega-functions "^5.10.0" vega-runtime "^6.1.3" vega-scenegraph "^4.9.2" vega-util "^1.15.2" @@ -28620,24 +28620,24 @@ vega-wordcloud@~4.1.3: vega-statistics "^1.7.9" vega-util "^1.15.2" -vega@^5.17.1: - version "5.17.1" - resolved "https://registry.yarnpkg.com/vega/-/vega-5.17.1.tgz#ac95144b40137201b9d71a13615cc5b6eac6e5f7" - integrity sha512-ev1S6ohnsyeqps/bUVbhByoAbucap8vXPuiAJcxxft/EpgQGbIX/x42l0ijc3U1QHow2Lr3khtE1RshyU4lW2w== +vega@^5.17.3: + version "5.17.3" + resolved "https://registry.yarnpkg.com/vega/-/vega-5.17.3.tgz#9901f24c8cf5ff2e98f3fddb372b8f5a6d8502d8" + integrity sha512-c8N2pNg9MMmC6shNpoxVw3aVp2XPFOgmWNX5BEOAdCaGHRnSgzNy44+gYdGRaIe6+ljTzZg99Mf+OLO50IP42A== dependencies: vega-crossfilter "~4.0.5" vega-dataflow "~5.7.3" vega-encode "~4.8.3" vega-event-selector "~2.0.6" - vega-expression "~3.0.1" + vega-expression "~4.0.1" vega-force "~4.0.7" vega-format "~1.0.4" - vega-functions "~5.8.0" + vega-functions "~5.10.0" vega-geo "~4.3.8" vega-hierarchy "~4.0.9" vega-label "~1.0.0" vega-loader "~4.4.0" - vega-parser "~6.1.0" + vega-parser "~6.1.2" vega-projection "~1.4.5" vega-regression "~1.0.9" vega-runtime "~6.1.3" @@ -28648,7 +28648,7 @@ vega@^5.17.1: vega-transforms "~4.9.3" vega-typings "~0.19.2" vega-util "~1.16.0" - vega-view "~5.9.0" + vega-view "~5.9.2" vega-view-transforms "~4.5.8" vega-voronoi "~4.1.5" vega-wordcloud "~4.1.3" From 6fc041c1d4a8bf9600977b089d1f4b1df40995e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Thu, 24 Dec 2020 09:36:58 +0100 Subject: [PATCH 52/72] filtering inventory page by transaction type (#86434) * filtering inventory page by transaction type * addressing pr comments * addressing pr comments * addressing pr comments * addressing pr comments * addressing pr comments * addressing pr comments * addressing pr comments * fixing test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../ServiceList/__fixtures__/props.json | 34 -- .../__fixtures__/service_api_mock_data.ts | 29 ++ .../service_inventory/ServiceList/index.tsx | 401 ++++++++++------- .../ServiceList/service_list.test.tsx | 46 +- .../lib/helpers/transaction_error_rate.ts | 5 +- .../__snapshots__/queries.test.ts.snap | 306 +++---------- .../get_services/get_health_statuses.ts | 56 +++ .../get_service_transaction_stats.ts | 199 +++++++++ .../get_services/get_services_items.ts | 41 +- .../get_services/get_services_items_stats.ts | 413 ------------------ .../basic/tests/services/top_services.ts | 24 +- 11 files changed, 624 insertions(+), 930 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/__fixtures__/props.json create mode 100644 x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/__fixtures__/service_api_mock_data.ts create mode 100644 x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts create mode 100644 x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts delete mode 100644 x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/__fixtures__/props.json b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/__fixtures__/props.json deleted file mode 100644 index 2e213c44bccf0..0000000000000 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/__fixtures__/props.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "items": [ - { - "serviceName": "opbeans-node", - "agentName": "nodejs", - "transactionsPerMinute": { - "value": 0, - "timeseries": [] - }, - "errorsPerMinute": { - "value": 46.06666666666667, - "timeseries": [] - }, - "environments": ["test"] - }, - { - "serviceName": "opbeans-python", - "agentName": "python", - "transactionsPerMinute": { - "value": 86.93333333333334, - "timeseries": [] - }, - "errorsPerMinute": { - "value": 12.6, - "timeseries": [] - }, - "avgResponseTime": { - "value": 91535.42944785276, - "timeseries": [] - }, - "environments": [] - } - ] -} diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/__fixtures__/service_api_mock_data.ts b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/__fixtures__/service_api_mock_data.ts new file mode 100644 index 0000000000000..04e1c9f8cbcab --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/__fixtures__/service_api_mock_data.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; + * you may not use this file except in compliance with the Elastic License. + */ +import { APIReturnType } from '../../../../../services/rest/createCallApmApi'; + +type ServiceListAPIResponse = APIReturnType<'GET /api/apm/services'>; + +export const items: ServiceListAPIResponse['items'] = [ + { + serviceName: 'opbeans-node', + transactionType: 'request', + agentName: 'nodejs', + transactionsPerMinute: { value: 0, timeseries: [] }, + transactionErrorRate: { value: 46.06666666666667, timeseries: [] }, + avgResponseTime: { value: null, timeseries: [] }, + environments: ['test'], + }, + { + serviceName: 'opbeans-python', + transactionType: 'page-load', + agentName: 'python', + transactionsPerMinute: { value: 86.93333333333334, timeseries: [] }, + transactionErrorRate: { value: 12.6, timeseries: [] }, + avgResponseTime: { value: 91535.42944785276, timeseries: [] }, + environments: [], + }, +]; diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx index 157d3ecc738a1..27a2cf6418ece 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx @@ -6,10 +6,16 @@ import { EuiFlexItem, EuiFlexGroup, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; import { ValuesType } from 'utility-types'; import { orderBy } from 'lodash'; +import { EuiIcon } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; +import { + TRANSACTION_PAGE_LOAD, + TRANSACTION_REQUEST, +} from '../../../../../common/transaction_types'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { ServiceHealthStatus } from '../../../../../common/service_health_status'; import { @@ -55,126 +61,6 @@ const ToolTipWrapper = styled.span` } `; -export const SERVICE_COLUMNS: Array> = [ - { - field: 'healthStatus', - name: i18n.translate('xpack.apm.servicesTable.healthColumnLabel', { - defaultMessage: 'Health', - }), - width: px(unit * 6), - sortable: true, - render: (_, { healthStatus }) => { - return ( - - ); - }, - }, - { - field: 'serviceName', - name: i18n.translate('xpack.apm.servicesTable.nameColumnLabel', { - defaultMessage: 'Name', - }), - width: '40%', - sortable: true, - render: (_, { serviceName, agentName }) => ( - - - - {agentName && ( - - - - )} - - - {formatString(serviceName)} - - - - - - ), - }, - { - field: 'environments', - name: i18n.translate('xpack.apm.servicesTable.environmentColumnLabel', { - defaultMessage: 'Environment', - }), - width: px(unit * 10), - sortable: true, - render: (_, { environments }) => ( - - ), - }, - { - field: 'avgResponseTime', - name: i18n.translate('xpack.apm.servicesTable.avgResponseTimeColumnLabel', { - defaultMessage: 'Avg. response time', - }), - sortable: true, - dataType: 'number', - render: (_, { avgResponseTime }) => ( - - ), - align: 'left', - width: px(unit * 10), - }, - { - field: 'transactionsPerMinute', - name: i18n.translate( - 'xpack.apm.servicesTable.transactionsPerMinuteColumnLabel', - { - defaultMessage: 'Trans. per minute', - } - ), - sortable: true, - dataType: 'number', - render: (_, { transactionsPerMinute }) => ( - - ), - align: 'left', - width: px(unit * 10), - }, - { - field: 'transactionErrorRate', - name: i18n.translate('xpack.apm.servicesTable.transactionErrorRate', { - defaultMessage: 'Error rate %', - }), - sortable: true, - dataType: 'number', - render: (_, { transactionErrorRate }) => { - const value = transactionErrorRate?.value; - - const valueLabel = asPercent(value, 1); - - return ( - - ); - }, - align: 'left', - width: px(unit * 10), - }, -]; - const SERVICE_HEALTH_STATUS_ORDER = [ ServiceHealthStatus.unknown, ServiceHealthStatus.healthy, @@ -182,59 +68,244 @@ const SERVICE_HEALTH_STATUS_ORDER = [ ServiceHealthStatus.critical, ]; +export function getServiceColumns({ + showTransactionTypeColumn, +}: { + showTransactionTypeColumn: boolean; +}): Array> { + return [ + { + field: 'healthStatus', + name: i18n.translate('xpack.apm.servicesTable.healthColumnLabel', { + defaultMessage: 'Health', + }), + width: px(unit * 6), + sortable: true, + render: (_, { healthStatus }) => { + return ( + + ); + }, + }, + { + field: 'serviceName', + name: i18n.translate('xpack.apm.servicesTable.nameColumnLabel', { + defaultMessage: 'Name', + }), + width: '40%', + sortable: true, + render: (_, { serviceName, agentName }) => ( + + + + {agentName && ( + + + + )} + + + {formatString(serviceName)} + + + + + + ), + }, + { + field: 'environments', + name: i18n.translate('xpack.apm.servicesTable.environmentColumnLabel', { + defaultMessage: 'Environment', + }), + width: px(unit * 10), + sortable: true, + render: (_, { environments }) => ( + + ), + }, + ...(showTransactionTypeColumn + ? [ + { + field: 'transactionType', + name: i18n.translate( + 'xpack.apm.servicesTable.transactionColumnLabel', + { + defaultMessage: 'Transaction type', + } + ), + width: px(unit * 10), + sortable: true, + }, + ] + : []), + { + field: 'avgResponseTime', + name: i18n.translate( + 'xpack.apm.servicesTable.avgResponseTimeColumnLabel', + { + defaultMessage: 'Avg. response time', + } + ), + sortable: true, + dataType: 'number', + render: (_, { avgResponseTime }) => ( + + ), + align: 'left', + width: px(unit * 10), + }, + { + field: 'transactionsPerMinute', + name: i18n.translate( + 'xpack.apm.servicesTable.transactionsPerMinuteColumnLabel', + { + defaultMessage: 'Trans. per minute', + } + ), + sortable: true, + dataType: 'number', + render: (_, { transactionsPerMinute }) => ( + + ), + align: 'left', + width: px(unit * 10), + }, + { + field: 'transactionErrorRate', + name: i18n.translate('xpack.apm.servicesTable.transactionErrorRate', { + defaultMessage: 'Error rate %', + }), + sortable: true, + dataType: 'number', + render: (_, { transactionErrorRate }) => { + const value = transactionErrorRate?.value; + + const valueLabel = asPercent(value, 1); + + return ( + + ); + }, + align: 'left', + width: px(unit * 10), + }, + ]; +} + export function ServiceList({ items, noItemsMessage }: Props) { const displayHealthStatus = items.some((item) => 'healthStatus' in item); + const showTransactionTypeColumn = items.some( + ({ transactionType }) => + transactionType !== TRANSACTION_REQUEST && + transactionType !== TRANSACTION_PAGE_LOAD + ); + + const serviceColumns = useMemo( + () => getServiceColumns({ showTransactionTypeColumn }), + [showTransactionTypeColumn] + ); + const columns = displayHealthStatus - ? SERVICE_COLUMNS - : SERVICE_COLUMNS.filter((column) => column.field !== 'healthStatus'); + ? serviceColumns + : serviceColumns.filter((column) => column.field !== 'healthStatus'); const initialSortField = displayHealthStatus ? 'healthStatus' : 'transactionsPerMinute'; return ( - { - // For healthStatus, sort items by healthStatus first, then by TPM - return sortField === 'healthStatus' - ? orderBy( - itemsToSort, - [ - (item) => { - return item.healthStatus - ? SERVICE_HEALTH_STATUS_ORDER.indexOf(item.healthStatus) - : -1; - }, - (item) => item.transactionsPerMinute?.value ?? 0, - ], - [sortDirection, sortDirection] - ) - : orderBy( - itemsToSort, - (item) => { - switch (sortField) { - // Use `?? -1` here so `undefined` will appear after/before `0`. - // In the table this will make the "N/A" items always at the - // bottom/top. - case 'avgResponseTime': - return item.avgResponseTime?.value ?? -1; - case 'transactionsPerMinute': - return item.transactionsPerMinute?.value ?? -1; - case 'transactionErrorRate': - return item.transactionErrorRate?.value ?? -1; - default: - return item[sortField as keyof typeof item]; + + + + + + )} + > + + + + + + {i18n.translate( + 'xpack.apm.servicesTable.metricsExplanationLabel', + { defaultMessage: 'What are these metrics?' } + )} + + + + + + { + // For healthStatus, sort items by healthStatus first, then by TPM + return sortField === 'healthStatus' + ? orderBy( + itemsToSort, + [ + (item) => { + return item.healthStatus + ? SERVICE_HEALTH_STATUS_ORDER.indexOf(item.healthStatus) + : -1; + }, + (item) => item.transactionsPerMinute?.value ?? 0, + ], + [sortDirection, sortDirection] + ) + : orderBy( + itemsToSort, + (item) => { + switch (sortField) { + // Use `?? -1` here so `undefined` will appear after/before `0`. + // In the table this will make the "N/A" items always at the + // bottom/top. + case 'avgResponseTime': + return item.avgResponseTime?.value ?? -1; + case 'transactionsPerMinute': + return item.transactionsPerMinute?.value ?? -1; + case 'transactionErrorRate': + return item.transactionErrorRate?.value ?? -1; + default: + return item[sortField as keyof typeof item]; + } + }, + sortDirection + ); + }} + /> + + ); } diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx index 1c6fa9fe0447e..45a4afeb53235 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx @@ -9,11 +9,8 @@ import { MemoryRouter } from 'react-router-dom'; import { ServiceHealthStatus } from '../../../../../common/service_health_status'; import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { mockMoment, renderWithTheme } from '../../../../utils/testHelpers'; -import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { ServiceList, SERVICE_COLUMNS } from './'; -import props from './__fixtures__/props.json'; - -type ServiceListAPIResponse = APIReturnType<'GET /api/apm/services'>; +import { getServiceColumns, ServiceList } from './'; +import { items } from './__fixtures__/service_api_mock_data'; function Wrapper({ children }: { children?: ReactNode }) { return ( @@ -36,10 +33,7 @@ describe('ServiceList', () => { it('renders with data', () => { expect(() => - renderWithTheme( - , - { wrapper: Wrapper } - ) + renderWithTheme(, { wrapper: Wrapper }) ).not.toThrowError(); }); @@ -61,9 +55,9 @@ describe('ServiceList', () => { }, environments: ['test'], }; - const renderedColumns = SERVICE_COLUMNS.map((c) => - c.render!(service[c.field!], service) - ); + const renderedColumns = getServiceColumns({ + showTransactionTypeColumn: false, + }).map((c) => c.render!(service[c.field!], service)); expect(renderedColumns[0]).toMatchInlineSnapshot(` { describe('without ML data', () => { it('does not render the health column', () => { - const { queryByText } = renderWithTheme( - , - { - wrapper: Wrapper, - } - ); + const { queryByText } = renderWithTheme(, { + wrapper: Wrapper, + }); const healthHeading = queryByText('Health'); expect(healthHeading).toBeNull(); }); it('sorts by transactions per minute', async () => { - const { findByTitle } = renderWithTheme( - , - { - wrapper: Wrapper, - } - ); + const { findByTitle } = renderWithTheme(, { + wrapper: Wrapper, + }); expect( await findByTitle('Trans. per minute; Sorted in descending order') @@ -103,12 +91,10 @@ describe('ServiceList', () => { it('renders the health column', async () => { const { findByTitle } = renderWithTheme( ({ - ...item, - healthStatus: ServiceHealthStatus.warning, - }) - )} + items={items.map((item) => ({ + ...item, + healthStatus: ServiceHealthStatus.warning, + }))} />, { wrapper: Wrapper } ); diff --git a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts index 536be56e152a3..876fc6b822213 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts @@ -18,7 +18,10 @@ export function getOutcomeAggregation({ searchAggregatedTransactions: boolean; }) { return { - terms: { field: EVENT_OUTCOME }, + terms: { + field: EVENT_OUTCOME, + include: [EventOutcome.failure, EventOutcome.success], + }, aggs: { // simply using the doc count to get the number of requests is not possible for transaction metrics (histograms) // to work around this we get the number of transactions by counting the number of latency values diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index a6818f96c728e..21402e4c8dac0 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -100,196 +100,27 @@ Array [ "aggs": Object { "services": Object { "aggs": Object { - "average": Object { - "avg": Object { - "field": "transaction.duration.us", - }, - }, - "timeseries": Object { + "transactionType": Object { "aggs": Object { - "average": Object { - "avg": Object { - "field": "transaction.duration.us", + "agentName": Object { + "top_hits": Object { + "docvalue_fields": Array [ + "agent.name", + ], + "size": 1, }, }, - }, - "date_histogram": Object { - "extended_bounds": Object { - "max": 1528977600000, - "min": 1528113600000, - }, - "field": "@timestamp", - "fixed_interval": "43200s", - "min_doc_count": 0, - }, - }, - }, - "terms": Object { - "field": "service.name", - "size": 500, - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - Object { - "term": Object { - "service.environment": "test", - }, - }, - ], - }, - }, - "size": 0, - }, - }, - Object { - "apm": Object { - "events": Array [ - "transaction", - "metric", - "error", - ], - }, - "body": Object { - "aggs": Object { - "services": Object { - "aggs": Object { - "agent_name": Object { - "top_hits": Object { - "_source": Array [ - "agent.name", - ], - "size": 1, - }, - }, - }, - "terms": Object { - "field": "service.name", - "size": 500, - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - Object { - "term": Object { - "service.environment": "test", - }, - }, - ], - }, - }, - "size": 0, - }, - }, - Object { - "apm": Object { - "events": Array [ - "transaction", - ], - }, - "body": Object { - "aggs": Object { - "services": Object { - "aggs": Object { - "count": Object { - "value_count": Object { - "field": "transaction.duration.us", - }, - }, - "timeseries": Object { - "aggs": Object { - "count": Object { - "value_count": Object { + "avg_duration": Object { + "avg": Object { "field": "transaction.duration.us", }, }, - }, - "date_histogram": Object { - "extended_bounds": Object { - "max": 1528977600000, - "min": 1528113600000, - }, - "field": "@timestamp", - "fixed_interval": "43200s", - "min_doc_count": 0, - }, - }, - }, - "terms": Object { - "field": "service.name", - "size": 500, - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - Object { - "term": Object { - "service.environment": "test", - }, - }, - ], - }, - }, - "size": 0, - }, - }, - Object { - "apm": Object { - "events": Array [ - "transaction", - ], - }, - "body": Object { - "aggs": Object { - "services": Object { - "aggs": Object { - "outcomes": Object { - "aggs": Object { - "count": Object { - "value_count": Object { - "field": "transaction.duration.us", + "environments": Object { + "terms": Object { + "field": "service.environment", + "missing": "", }, }, - }, - "terms": Object { - "field": "event.outcome", - }, - }, - "timeseries": Object { - "aggs": Object { "outcomes": Object { "aggs": Object { "count": Object { @@ -300,73 +131,62 @@ Array [ }, "terms": Object { "field": "event.outcome", + "include": Array [ + "failure", + "success", + ], }, }, - }, - "date_histogram": Object { - "extended_bounds": Object { - "max": 1528977600000, - "min": 1528113600000, + "real_document_count": Object { + "value_count": Object { + "field": "transaction.duration.us", + }, }, - "field": "@timestamp", - "fixed_interval": "43200s", - "min_doc_count": 0, - }, - }, - }, - "terms": Object { - "field": "service.name", - "size": 500, - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, + "timeseries": Object { + "aggs": Object { + "avg_duration": Object { + "avg": Object { + "field": "transaction.duration.us", + }, + }, + "outcomes": Object { + "aggs": Object { + "count": Object { + "value_count": Object { + "field": "transaction.duration.us", + }, + }, + }, + "terms": Object { + "field": "event.outcome", + "include": Array [ + "failure", + "success", + ], + }, + }, + "real_document_count": Object { + "value_count": Object { + "field": "transaction.duration.us", + }, + }, + }, + "date_histogram": Object { + "extended_bounds": Object { + "max": 1528977600000, + "min": 1528113600000, + }, + "field": "@timestamp", + "fixed_interval": "43200s", + "min_doc_count": 0, + }, }, }, - }, - Object { - "term": Object { - "service.environment": "test", - }, - }, - Object { - "terms": Object { - "event.outcome": Array [ - "failure", - "success", - ], - }, - }, - ], - }, - }, - "size": 0, - }, - }, - Object { - "apm": Object { - "events": Array [ - "transaction", - "metric", - "error", - ], - }, - "body": Object { - "aggs": Object { - "services": Object { - "aggs": Object { - "environments": Object { "terms": Object { - "field": "service.environment", - "size": 100, + "field": "transaction.type", + "order": Object { + "real_document_count": "desc", + }, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts new file mode 100644 index 0000000000000..206827a744113 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getSeverity } from '../../../../common/anomaly_detection'; +import { getServiceHealthStatus } from '../../../../common/service_health_status'; +import { + getMLJobIds, + getServiceAnomalies, +} from '../../service_map/get_service_anomalies'; +import { + ServicesItemsProjection, + ServicesItemsSetup, +} from './get_services_items'; + +interface AggregationParams { + setup: ServicesItemsSetup; + projection: ServicesItemsProjection; + searchAggregatedTransactions: boolean; +} + +export const getHealthStatuses = async ( + { setup }: AggregationParams, + mlAnomaliesEnvironment?: string +) => { + if (!setup.ml) { + return []; + } + + const jobIds = await getMLJobIds( + setup.ml.anomalyDetectors, + mlAnomaliesEnvironment + ); + if (!jobIds.length) { + return []; + } + + const anomalies = await getServiceAnomalies({ + setup, + environment: mlAnomaliesEnvironment, + }); + + return Object.keys(anomalies.serviceAnomalies).map((serviceName) => { + const stats = anomalies.serviceAnomalies[serviceName]; + + const severity = getSeverity(stats.anomalyScore); + const healthStatus = getServiceHealthStatus({ severity }); + + return { + serviceName, + healthStatus, + }; + }); +}; diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts new file mode 100644 index 0000000000000..0ee7080dc0834 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + AGENT_NAME, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_TYPE, +} from '../../../../common/elasticsearch_fieldnames'; +import { + TRANSACTION_PAGE_LOAD, + TRANSACTION_REQUEST, +} from '../../../../common/transaction_types'; +import { rangeFilter } from '../../../../common/utils/range_filter'; +import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; +import { + getDocumentTypeFilterForAggregatedTransactions, + getProcessorEventForAggregatedTransactions, + getTransactionDurationFieldForAggregatedTransactions, +} from '../../helpers/aggregated_transactions'; +import { getBucketSize } from '../../helpers/get_bucket_size'; +import { + calculateTransactionErrorPercentage, + getOutcomeAggregation, +} from '../../helpers/transaction_error_rate'; +import { ServicesItemsSetup } from './get_services_items'; + +interface AggregationParams { + setup: ServicesItemsSetup; + searchAggregatedTransactions: boolean; +} + +const MAX_NUMBER_OF_SERVICES = 500; + +function calculateAvgDuration({ + value, + deltaAsMinutes, +}: { + value: number; + deltaAsMinutes: number; +}) { + return value / deltaAsMinutes; +} + +export async function getServiceTransactionStats({ + setup, + searchAggregatedTransactions, +}: AggregationParams) { + const { apmEventClient, start, end, esFilter } = setup; + + const outcomes = getOutcomeAggregation({ searchAggregatedTransactions }); + + const metrics = { + real_document_count: { + value_count: { + field: getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ), + }, + }, + avg_duration: { + avg: { + field: getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ), + }, + }, + outcomes, + }; + + const response = await apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + { range: rangeFilter(start, end) }, + ...esFilter, + ...getDocumentTypeFilterForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + }, + aggs: { + services: { + terms: { + field: SERVICE_NAME, + size: MAX_NUMBER_OF_SERVICES, + }, + aggs: { + transactionType: { + terms: { + field: TRANSACTION_TYPE, + order: { real_document_count: 'desc' }, + }, + aggs: { + ...metrics, + environments: { + terms: { + field: SERVICE_ENVIRONMENT, + missing: '', + }, + }, + agentName: { + top_hits: { + docvalue_fields: [AGENT_NAME] as const, + size: 1, + }, + }, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: getBucketSize({ + start, + end, + numBuckets: 20, + }).intervalString, + min_doc_count: 0, + extended_bounds: { min: start, max: end }, + }, + aggs: metrics, + }, + }, + }, + }, + }, + }, + }, + }); + + const deltaAsMinutes = (setup.end - setup.start) / 1000 / 60; + + return ( + response.aggregations?.services.buckets.map((bucket) => { + const topTransactionTypeBucket = + bucket.transactionType.buckets.find( + ({ key }) => + key === TRANSACTION_REQUEST || key === TRANSACTION_PAGE_LOAD + ) ?? bucket.transactionType.buckets[0]; + + return { + serviceName: bucket.key as string, + transactionType: topTransactionTypeBucket.key as string, + environments: topTransactionTypeBucket.environments.buckets + .map((environmentBucket) => environmentBucket.key as string) + .filter(Boolean), + agentName: topTransactionTypeBucket.agentName.hits.hits[0].fields[ + 'agent.name' + ]?.[0] as AgentName, + avgResponseTime: { + value: topTransactionTypeBucket.avg_duration.value, + timeseries: topTransactionTypeBucket.timeseries.buckets.map( + (dateBucket) => ({ + x: dateBucket.key, + y: dateBucket.avg_duration.value, + }) + ), + }, + transactionErrorRate: { + value: calculateTransactionErrorPercentage( + topTransactionTypeBucket.outcomes + ), + timeseries: topTransactionTypeBucket.timeseries.buckets.map( + (dateBucket) => ({ + x: dateBucket.key, + y: calculateTransactionErrorPercentage(dateBucket.outcomes), + }) + ), + }, + transactionsPerMinute: { + value: calculateAvgDuration({ + value: topTransactionTypeBucket.real_document_count.value, + deltaAsMinutes, + }), + timeseries: topTransactionTypeBucket.timeseries.buckets.map( + (dateBucket) => ({ + x: dateBucket.key, + y: calculateAvgDuration({ + value: dateBucket.real_document_count.value, + deltaAsMinutes, + }), + }) + ), + }, + }; + }) ?? [] + ); +} diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts index 11f3e44fce87c..359c677b00baf 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts @@ -7,14 +7,8 @@ import { Logger } from '@kbn/logging'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { getServicesProjection } from '../../../projections/services'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -import { - getAgentNames, - getEnvironments, - getHealthStatuses, - getTransactionDurationAverages, - getTransactionErrorRates, - getTransactionRates, -} from './get_services_items_stats'; +import { getHealthStatuses } from './get_health_statuses'; +import { getServiceTransactionStats } from './get_service_transaction_stats'; export type ServicesItemsSetup = Setup & SetupTimeRange; export type ServicesItemsProjection = ReturnType; @@ -37,46 +31,23 @@ export async function getServicesItems({ searchAggregatedTransactions, }; - const [ - transactionDurationAverages, - agentNames, - transactionRates, - transactionErrorRates, - environments, - healthStatuses, - ] = await Promise.all([ - getTransactionDurationAverages(params), - getAgentNames(params), - getTransactionRates(params), - getTransactionErrorRates(params), - getEnvironments(params), + const [transactionStats, healthStatuses] = await Promise.all([ + getServiceTransactionStats(params), getHealthStatuses(params, setup.uiFilters.environment).catch((err) => { logger.error(err); return []; }), ]); - const apmServiceMetrics = joinByKey( - [ - ...transactionDurationAverages, - ...agentNames, - ...transactionRates, - ...transactionErrorRates, - ...environments, - ], - 'serviceName' - ); - - const apmServices = apmServiceMetrics.map(({ serviceName }) => serviceName); + const apmServices = transactionStats.map(({ serviceName }) => serviceName); // make sure to exclude health statuses from services // that are not found in APM data - const matchedHealthStatuses = healthStatuses.filter(({ serviceName }) => apmServices.includes(serviceName) ); - const allMetrics = [...apmServiceMetrics, ...matchedHealthStatuses]; + const allMetrics = [...transactionStats, ...matchedHealthStatuses]; return joinByKey(allMetrics, 'serviceName'); } diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts deleted file mode 100644 index c8ebaa13d9df9..0000000000000 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts +++ /dev/null @@ -1,413 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getServiceHealthStatus } from '../../../../common/service_health_status'; -import { EventOutcome } from '../../../../common/event_outcome'; -import { getSeverity } from '../../../../common/anomaly_detection'; -import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; -import { - AGENT_NAME, - SERVICE_ENVIRONMENT, - EVENT_OUTCOME, -} from '../../../../common/elasticsearch_fieldnames'; -import { mergeProjection } from '../../../projections/util/merge_projection'; -import { - ServicesItemsSetup, - ServicesItemsProjection, -} from './get_services_items'; -import { - getDocumentTypeFilterForAggregatedTransactions, - getProcessorEventForAggregatedTransactions, - getTransactionDurationFieldForAggregatedTransactions, -} from '../../helpers/aggregated_transactions'; -import { getBucketSize } from '../../helpers/get_bucket_size'; -import { - getMLJobIds, - getServiceAnomalies, -} from '../../service_map/get_service_anomalies'; -import { - calculateTransactionErrorPercentage, - getOutcomeAggregation, - getTransactionErrorRateTimeSeries, -} from '../../helpers/transaction_error_rate'; - -function getDateHistogramOpts(start: number, end: number) { - return { - field: '@timestamp', - fixed_interval: getBucketSize({ start, end, numBuckets: 20 }) - .intervalString, - min_doc_count: 0, - extended_bounds: { min: start, max: end }, - }; -} - -const MAX_NUMBER_OF_SERVICES = 500; - -const getDeltaAsMinutes = (setup: ServicesItemsSetup) => - (setup.end - setup.start) / 1000 / 60; - -interface AggregationParams { - setup: ServicesItemsSetup; - projection: ServicesItemsProjection; - searchAggregatedTransactions: boolean; -} - -export const getTransactionDurationAverages = async ({ - setup, - projection, - searchAggregatedTransactions, -}: AggregationParams) => { - const { apmEventClient, start, end } = setup; - - const response = await apmEventClient.search( - mergeProjection(projection, { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - ...projection.body.query.bool.filter, - ...getDocumentTypeFilterForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - }, - aggs: { - services: { - terms: { - ...projection.body.aggs.services.terms, - size: MAX_NUMBER_OF_SERVICES, - }, - aggs: { - average: { - avg: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), - }, - }, - timeseries: { - date_histogram: getDateHistogramOpts(start, end), - aggs: { - average: { - avg: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), - }, - }, - }, - }, - }, - }, - }, - }, - }) - ); - - const { aggregations } = response; - - if (!aggregations) { - return []; - } - - return aggregations.services.buckets.map((serviceBucket) => ({ - serviceName: serviceBucket.key as string, - avgResponseTime: { - value: serviceBucket.average.value, - timeseries: serviceBucket.timeseries.buckets.map((dateBucket) => ({ - x: dateBucket.key, - y: dateBucket.average.value, - })), - }, - })); -}; - -export const getAgentNames = async ({ - setup, - projection, -}: AggregationParams) => { - const { apmEventClient } = setup; - const response = await apmEventClient.search( - mergeProjection(projection, { - body: { - size: 0, - aggs: { - services: { - terms: { - ...projection.body.aggs.services.terms, - size: MAX_NUMBER_OF_SERVICES, - }, - aggs: { - agent_name: { - top_hits: { - _source: [AGENT_NAME], - size: 1, - }, - }, - }, - }, - }, - }, - }) - ); - - const { aggregations } = response; - - if (!aggregations) { - return []; - } - - return aggregations.services.buckets.map((serviceBucket) => ({ - serviceName: serviceBucket.key as string, - agentName: serviceBucket.agent_name.hits.hits[0]?._source.agent - .name as AgentName, - })); -}; - -export const getTransactionRates = async ({ - setup, - projection, - searchAggregatedTransactions, -}: AggregationParams) => { - const { apmEventClient, start, end } = setup; - const response = await apmEventClient.search( - mergeProjection(projection, { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - ...projection.body.query.bool.filter, - ...getDocumentTypeFilterForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - }, - aggs: { - services: { - terms: { - ...projection.body.aggs.services.terms, - size: MAX_NUMBER_OF_SERVICES, - }, - aggs: { - count: { - value_count: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), - }, - }, - timeseries: { - date_histogram: getDateHistogramOpts(start, end), - aggs: { - count: { - value_count: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), - }, - }, - }, - }, - }, - }, - }, - }, - }) - ); - - const { aggregations } = response; - - if (!aggregations) { - return []; - } - - const deltaAsMinutes = getDeltaAsMinutes(setup); - - return aggregations.services.buckets.map((serviceBucket) => { - const transactionsPerMinute = serviceBucket.count.value / deltaAsMinutes; - return { - serviceName: serviceBucket.key as string, - transactionsPerMinute: { - value: transactionsPerMinute, - timeseries: serviceBucket.timeseries.buckets.map((dateBucket) => ({ - x: dateBucket.key, - y: dateBucket.count.value / deltaAsMinutes, - })), - }, - }; - }); -}; - -export const getTransactionErrorRates = async ({ - setup, - projection, - searchAggregatedTransactions, -}: AggregationParams) => { - const { apmEventClient, start, end } = setup; - - const outcomes = getOutcomeAggregation({ searchAggregatedTransactions }); - - const response = await apmEventClient.search( - mergeProjection(projection, { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - ...projection.body.query.bool.filter, - { - terms: { - [EVENT_OUTCOME]: [EventOutcome.failure, EventOutcome.success], - }, - }, - ], - }, - }, - aggs: { - services: { - terms: { - ...projection.body.aggs.services.terms, - size: MAX_NUMBER_OF_SERVICES, - }, - aggs: { - outcomes, - timeseries: { - date_histogram: getDateHistogramOpts(start, end), - aggs: { - outcomes, - }, - }, - }, - }, - }, - }, - }) - ); - - const { aggregations } = response; - - if (!aggregations) { - return []; - } - - return aggregations.services.buckets.map((serviceBucket) => { - const transactionErrorRate = calculateTransactionErrorPercentage( - serviceBucket.outcomes - ); - return { - serviceName: serviceBucket.key as string, - transactionErrorRate: { - value: transactionErrorRate, - timeseries: getTransactionErrorRateTimeSeries( - serviceBucket.timeseries.buckets - ), - }, - }; - }); -}; - -export const getEnvironments = async ({ - setup, - projection, -}: AggregationParams) => { - const { apmEventClient, config } = setup; - const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; - const response = await apmEventClient.search( - mergeProjection(projection, { - body: { - size: 0, - aggs: { - services: { - terms: { - ...projection.body.aggs.services.terms, - size: MAX_NUMBER_OF_SERVICES, - }, - aggs: { - environments: { - terms: { - field: SERVICE_ENVIRONMENT, - size: maxServiceEnvironments, - }, - }, - }, - }, - }, - }, - }) - ); - - const { aggregations } = response; - - if (!aggregations) { - return []; - } - - return aggregations.services.buckets.map((serviceBucket) => ({ - serviceName: serviceBucket.key as string, - environments: serviceBucket.environments.buckets.map( - (envBucket) => envBucket.key as string - ), - })); -}; - -export const getHealthStatuses = async ( - { setup }: AggregationParams, - mlAnomaliesEnvironment?: string -) => { - if (!setup.ml) { - return []; - } - - const jobIds = await getMLJobIds( - setup.ml.anomalyDetectors, - mlAnomaliesEnvironment - ); - if (!jobIds.length) { - return []; - } - - const anomalies = await getServiceAnomalies({ - setup, - environment: mlAnomaliesEnvironment, - }); - - return Object.keys(anomalies.serviceAnomalies).map((serviceName) => { - const stats = anomalies.serviceAnomalies[serviceName]; - - const severity = getSeverity(stats.anomalyScore); - const healthStatus = getServiceHealthStatus({ severity }); - - return { - serviceName, - healthStatus, - }; - }); -}; diff --git a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts index 52c9dd74167f5..b160677673331 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts @@ -99,21 +99,24 @@ export default function ApiTest({ getService }: FtrProviderContext) { Array [ Object { "avgResponseTime": Object { - "value": 556200.153101878, + "value": 420419.34550767, }, "transactionErrorRate": Object { "value": 0, }, "transactionsPerMinute": Object { - "value": 117.133333333333, + "value": 45.6333333333333, }, }, Object { "avgResponseTime": Object { - "value": 2629229.16666667, + "value": 2382833.33333333, + }, + "transactionErrorRate": Object { + "value": null, }, "transactionsPerMinute": Object { - "value": 3.2, + "value": 0.2, }, }, Object { @@ -151,24 +154,24 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, Object { "avgResponseTime": Object { - "value": 563605.417040359, + "value": 24920.1052631579, }, "transactionErrorRate": Object { "value": 0.0210526315789474, }, "transactionsPerMinute": Object { - "value": 7.43333333333333, + "value": 3.16666666666667, }, }, Object { "avgResponseTime": Object { - "value": 217138.013645224, + "value": 29542.6607142857, }, "transactionErrorRate": Object { - "value": 0.315789473684211, + "value": 0.0357142857142857, }, "transactionsPerMinute": Object { - "value": 17.1, + "value": 1.86666666666667, }, }, Object { @@ -186,6 +189,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { "avgResponseTime": Object { "value": 2319812.5, }, + "transactionErrorRate": Object { + "value": null, + }, "transactionsPerMinute": Object { "value": 0.533333333333333, }, From 47ce575cc0d21cb87482a27543419b8b9bd756cb Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 24 Dec 2020 13:52:09 +0100 Subject: [PATCH 53/72] [ML] Fix charts grid on the Anomaly Explorer page (#86904) * [ML] fix AR charts grid items width * [ML] update test snapshot --- .../explorer_charts_container_service.test.js.snap | 4 ++-- .../explorer_charts_container_service.js | 10 +++++----- .../application/explorer/swimlane_container.tsx | 11 ++++------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap b/x-pack/plugins/ml/public/application/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap index 64638fcd23734..ecb7fc07711e8 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap @@ -596,8 +596,8 @@ Object { "loading": false, "metricFieldName": "responsetime", "metricFunction": "avg", - "plotEarliest": 1486560600000, - "plotLatest": 1486765800000, + "plotEarliest": 1486542600000, + "plotLatest": 1486783800000, "selectedEarliest": 1486656000000, "selectedLatest": 1486670399999, "timeField": "@timestamp", diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js index 47087e776d6dd..a2c530c9ca494 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js @@ -29,6 +29,7 @@ import { explorerService } from '../explorer_dashboard_service'; import { CHART_TYPE } from '../explorer_constants'; import { i18n } from '@kbn/i18n'; +import { SWIM_LANE_LABEL_WIDTH } from '../swimlane_container'; export function getDefaultChartsData() { return { @@ -57,15 +58,14 @@ export const anomalyDataChange = function ( ) { const data = getDefaultChartsData(); + const containerWith = chartsContainerWidth + SWIM_LANE_LABEL_WIDTH; + const filteredRecords = anomalyRecords.filter((record) => { return Number(record.record_score) >= severity; }); const [allSeriesRecords, errorMessages] = processRecordsForDisplay(filteredRecords); // Calculate the number of charts per row, depending on the width available, to a max of 4. - let chartsPerRow = Math.min( - Math.max(Math.floor(chartsContainerWidth / 550), 1), - MAX_CHARTS_PER_ROW - ); + let chartsPerRow = Math.min(Math.max(Math.floor(containerWith / 550), 1), MAX_CHARTS_PER_ROW); if (allSeriesRecords.length === 1) { chartsPerRow = 1; } @@ -81,7 +81,7 @@ export const anomalyDataChange = function ( // Calculate the time range of the charts, which is a function of the chart width and max job bucket span. data.tooManyBuckets = false; - const chartWidth = Math.floor(chartsContainerWidth / chartsPerRow); + const chartWidth = Math.floor(containerWith / chartsPerRow); const { chartRange, tooManyBuckets } = calculateChartRange( seriesConfigs, selectedEarliestMs, diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index 101d4857a89b1..145f6cc0fcf7a 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -46,11 +46,11 @@ import { useUiSettings } from '../contexts/kibana'; /** * Ignore insignificant resize, e.g. browser scrollbar appearance. */ -const RESIZE_IGNORED_DIFF_PX = 20; const RESIZE_THROTTLE_TIME_MS = 500; const CELL_HEIGHT = 30; const LEGEND_HEIGHT = 34; const Y_AXIS_HEIGHT = 24; +export const SWIM_LANE_LABEL_WIDTH = 200; export function isViewBySwimLaneData(arg: any): arg is ViewBySwimLaneData { return arg && arg.hasOwnProperty('cardinality'); @@ -167,12 +167,9 @@ export const SwimlaneContainer: FC = ({ const resizeHandler = useCallback( throttle((e: { width: number; height: number }) => { - const labelWidth = 200; - const resultNewWidth = e.width - labelWidth; - if (Math.abs(resultNewWidth - chartWidth) > RESIZE_IGNORED_DIFF_PX) { - setChartWidth(resultNewWidth); - onResize(resultNewWidth); - } + const resultNewWidth = e.width - SWIM_LANE_LABEL_WIDTH; + setChartWidth(resultNewWidth); + onResize(resultNewWidth); }, RESIZE_THROTTLE_TIME_MS), [chartWidth] ); From c0d6e12c3c1a06c2499ca505b8d8fc9bda4812b2 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Mon, 28 Dec 2020 08:08:04 +0100 Subject: [PATCH 54/72] Removes archives (#86537) --- .../security_solution/cypress/README.md | 11 +- .../cypress/integration/alerts.spec.ts | 139 +- ...alerts_detection_callouts_readonly.spec.ts | 3 +- .../alerts_detection_exceptions.spec.ts | 5 +- .../alerts_detection_rules.spec.ts | 39 +- .../alerts_detection_rules_custom.spec.ts | 49 +- .../alerts_detection_rules_eql.spec.ts | 7 +- .../alerts_detection_rules_export.spec.ts | 5 +- ...ts_detection_rules_indicator_match.spec.ts | 2 - .../alerts_detection_rules_ml.spec.ts | 4 +- .../alerts_detection_rules_override.spec.ts | 6 +- .../alerts_detection_rules_prebuilt.spec.ts | 9 +- .../alerts_detection_rules_threshold.spec.ts | 2 - .../integration/alerts_timeline.spec.ts | 24 +- .../cypress/integration/cases.spec.ts | 4 +- .../cases_connector_options.spec.ts | 2 +- .../integration/cases_connectors.spec.ts | 2 +- .../cypress/integration/events_viewer.spec.ts | 14 +- .../integration/fields_browser.spec.ts | 28 +- .../cypress/integration/sourcerer.spec.ts | 2 +- .../timeline_attach_to_case.spec.ts | 30 +- .../integration/timeline_creation.spec.ts | 15 +- .../timeline_local_storage.spec.ts | 2 +- .../timeline_template_creation.spec.ts | 2 +- .../timeline_templates_export.spec.ts | 2 +- .../timeline_toggle_column.spec.ts | 8 +- .../integration/timelines_export.spec.ts | 2 +- .../integration/url_compatibility.spec.ts | 2 +- .../cypress/integration/value_lists.spec.ts | 3 - .../security_solution/cypress/objects/rule.ts | 17 +- .../cypress/screens/alerts_detection_rules.ts | 2 + .../cypress/screens/create_new_rule.ts | 3 +- .../cypress/screens/timeline.ts | 4 +- .../cypress/tasks/api_calls/cases.ts | 28 + .../cypress/tasks/api_calls/rules.ts | 31 +- .../security_solution/cypress/tasks/common.ts | 19 + .../cypress/tasks/hosts/events.ts | 2 +- .../cypress/tasks/timeline.ts | 7 +- .../es_archives/alerts/data.json.gz | Bin 58602 -> 0 bytes .../es_archives/alerts/mappings.json | 8124 -------------- .../case_and_timeline/data.json.gz | Bin 3687 -> 0 bytes .../case_and_timeline/mappings.json | 2616 ----- .../es_archives/closed_alerts/data.json.gz | Bin 55877 -> 0 bytes .../es_archives/closed_alerts/mappings.json | 8124 -------------- .../es_archives/custom_rules/data.json.gz | Bin 3084 -> 0 bytes .../es_archives/custom_rules/mappings.json | 6243 ----------- .../prebuilt_rules_loaded/data.json.gz | Bin 42571 -> 0 bytes .../prebuilt_rules_loaded/mappings.json | 2967 ----- .../es_archives/timeline_alerts/data.json.gz | Bin 225608 -> 0 bytes .../es_archives/timeline_alerts/mappings.json | 9588 ----------------- 50 files changed, 281 insertions(+), 37917 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/tasks/api_calls/cases.ts delete mode 100644 x-pack/test/security_solution_cypress/es_archives/alerts/data.json.gz delete mode 100644 x-pack/test/security_solution_cypress/es_archives/alerts/mappings.json delete mode 100644 x-pack/test/security_solution_cypress/es_archives/case_and_timeline/data.json.gz delete mode 100644 x-pack/test/security_solution_cypress/es_archives/case_and_timeline/mappings.json delete mode 100644 x-pack/test/security_solution_cypress/es_archives/closed_alerts/data.json.gz delete mode 100644 x-pack/test/security_solution_cypress/es_archives/closed_alerts/mappings.json delete mode 100644 x-pack/test/security_solution_cypress/es_archives/custom_rules/data.json.gz delete mode 100644 x-pack/test/security_solution_cypress/es_archives/custom_rules/mappings.json delete mode 100644 x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/data.json.gz delete mode 100644 x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/mappings.json delete mode 100644 x-pack/test/security_solution_cypress/es_archives/timeline_alerts/data.json.gz delete mode 100644 x-pack/test/security_solution_cypress/es_archives/timeline_alerts/mappings.json diff --git a/x-pack/plugins/security_solution/cypress/README.md b/x-pack/plugins/security_solution/cypress/README.md index b82f4a392483c..4fb98f0983ee9 100644 --- a/x-pack/plugins/security_solution/cypress/README.md +++ b/x-pack/plugins/security_solution/cypress/README.md @@ -179,9 +179,9 @@ CYPRESS_BASE_URL=http(s)://:@ CYPRESS_ELASTICSEARCH_ ## Best Practices -### Clean up the state between tests +### Clean up the state -Remember to clean up the state of the test after its execution. +Remember to use the `cleanKibana` method before starting the execution of the test ### Minimize the use of es_archive @@ -192,15 +192,12 @@ When possible, create all the data that you need for executing the tests using t Loading the web page takes a big amount of time, in order to minimize that impact, the following points should be taken into consideration until another solution is implemented: -- Don't refresh the page for every test to clean the state of it. -- Instead, group the tests that are similar in different contexts. +- Group the tests that are similar in different contexts. - For every context login only once, clean the state between tests if needed without re-loading the page. - All tests in a spec file must be order-independent. - - If you need to reload the page to make the tests order-independent, consider to create a new context. - + Remember that minimizing the number of times the web page is loaded, we minimize as well the execution time. - ## Reporting When Cypress tests are run on the command line via non visual mode diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts index a15aad1bd8cc3..82e214398f69a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { newRule } from '../objects/rule'; import { ALERTS, ALERTS_COUNT, @@ -24,11 +25,13 @@ import { waitForAlertsToBeLoaded, markInProgressFirstAlert, goToInProgressAlerts, + waitForAlertsIndexToBeCreated, } from '../tasks/alerts'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createCustomRuleActivated } from '../tasks/api_calls/rules'; import { cleanKibana } from '../tasks/common'; -import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { waitForAlertsToPopulate } from '../tasks/create_new_rule'; import { loginAndWaitForPage } from '../tasks/login'; +import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; @@ -36,25 +39,21 @@ describe('Alerts', () => { context('Closing alerts', () => { beforeEach(() => { cleanKibana(); - removeSignalsIndex(); - esArchiverLoad('alerts'); loginAndWaitForPage(DETECTIONS_URL); - }); - - afterEach(() => { - esArchiverUnload('alerts'); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + createCustomRuleActivated(newRule); + refreshPage(); + waitForAlertsToPopulate(); }); it('Closes and opens alerts', () => { - waitForAlertsPanelToBeLoaded(); - waitForAlertsToBeLoaded(); - + const numberOfAlertsToBeClosed = 3; cy.get(ALERTS_COUNT) .invoke('text') .then((numberOfAlerts) => { cy.get(SHOWING_ALERTS).should('have.text', `Showing ${numberOfAlerts} alerts`); - const numberOfAlertsToBeClosed = 3; selectNumberOfAlerts(numberOfAlertsToBeClosed); cy.get(SELECTED_ALERTS).should( @@ -64,8 +63,6 @@ describe('Alerts', () => { closeAlerts(); waitForAlerts(); - cy.reload(); - waitForAlerts(); const expectedNumberOfAlertsAfterClosing = +numberOfAlerts - numberOfAlertsToBeClosed; cy.get(ALERTS_COUNT).should('have.text', expectedNumberOfAlertsAfterClosing.toString()); @@ -92,11 +89,6 @@ describe('Alerts', () => { openAlerts(); waitForAlerts(); - cy.reload(); - waitForAlertsToBeLoaded(); - waitForAlerts(); - goToClosedAlerts(); - waitForAlerts(); const expectedNumberOfClosedAlertsAfterOpened = 2; cy.get(ALERTS_COUNT).should( @@ -124,8 +116,6 @@ describe('Alerts', () => { }); it('Closes one alert when more than one opened alerts are selected', () => { - waitForAlertsToBeLoaded(); - cy.get(ALERTS_COUNT) .invoke('text') .then((numberOfAlerts) => { @@ -137,8 +127,6 @@ describe('Alerts', () => { cy.get(TAKE_ACTION_POPOVER_BTN).should('not.have.attr', 'disabled'); closeFirstAlert(); - cy.reload(); - waitForAlertsToBeLoaded(); waitForAlerts(); const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeClosed; @@ -164,52 +152,66 @@ describe('Alerts', () => { context('Opening alerts', () => { beforeEach(() => { cleanKibana(); - removeSignalsIndex(); - esArchiverLoad('closed_alerts'); loginAndWaitForPage(DETECTIONS_URL); - }); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + createCustomRuleActivated(newRule); + refreshPage(); + waitForAlertsToPopulate(); + selectNumberOfAlerts(5); + + cy.get(SELECTED_ALERTS).should('have.text', `Selected 5 alerts`); - afterEach(() => { - esArchiverUnload('closed_alerts'); + closeAlerts(); + waitForAlerts(); + refreshPage(); }); it('Open one alert when more than one closed alerts are selected', () => { - waitForAlerts(); - goToClosedAlerts(); - waitForAlertsToBeLoaded(); + waitForAlertsToPopulate(); cy.get(ALERTS_COUNT) .invoke('text') - .then((numberOfAlerts) => { - const numberOfAlertsToBeOpened = 1; - const numberOfAlertsToBeSelected = 3; - - cy.get(TAKE_ACTION_POPOVER_BTN).should('have.attr', 'disabled'); - selectNumberOfAlerts(numberOfAlertsToBeSelected); - cy.get(TAKE_ACTION_POPOVER_BTN).should('not.have.attr', 'disabled'); - - openFirstAlert(); - cy.reload(); + .then((numberOfOpenedAlertsText) => { + const numberOfOpenedAlerts = parseInt(numberOfOpenedAlertsText, 10); goToClosedAlerts(); - waitForAlertsToBeLoaded(); - waitForAlerts(); - - const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeOpened; - cy.get(ALERTS_COUNT).should('have.text', expectedNumberOfAlerts.toString()); - cy.get(SHOWING_ALERTS).should( - 'have.text', - `Showing ${expectedNumberOfAlerts.toString()} alerts` - ); - - goToOpenedAlerts(); - waitForAlerts(); - - cy.get(ALERTS_COUNT).should('have.text', numberOfAlertsToBeOpened.toString()); - cy.get(SHOWING_ALERTS).should( - 'have.text', - `Showing ${numberOfAlertsToBeOpened.toString()} alert` - ); - cy.get(ALERTS).should('have.length', numberOfAlertsToBeOpened); + cy.get(ALERTS_COUNT) + .invoke('text') + .then((numberOfAlerts) => { + const numberOfAlertsToBeOpened = 1; + const numberOfAlertsToBeSelected = 3; + + cy.get(TAKE_ACTION_POPOVER_BTN).should('have.attr', 'disabled'); + selectNumberOfAlerts(numberOfAlertsToBeSelected); + cy.get(SELECTED_ALERTS).should( + 'have.text', + `Selected ${numberOfAlertsToBeSelected} alerts` + ); + + cy.get(TAKE_ACTION_POPOVER_BTN).should('not.have.attr', 'disabled'); + + openFirstAlert(); + waitForAlerts(); + + const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeOpened; + cy.get(ALERTS_COUNT).should('have.text', expectedNumberOfAlerts.toString()); + cy.get(SHOWING_ALERTS).should( + 'have.text', + `Showing ${expectedNumberOfAlerts.toString()} alerts` + ); + + goToOpenedAlerts(); + waitForAlerts(); + + cy.get(ALERTS_COUNT).should( + 'have.text', + (numberOfOpenedAlerts + numberOfAlertsToBeOpened).toString() + ); + cy.get(SHOWING_ALERTS).should( + 'have.text', + `Showing ${(numberOfOpenedAlerts + numberOfAlertsToBeOpened).toString()} alerts` + ); + }); }); }); }); @@ -217,20 +219,15 @@ describe('Alerts', () => { context('Marking alerts as in-progress', () => { beforeEach(() => { cleanKibana(); - removeSignalsIndex(); - esArchiverLoad('alerts'); loginAndWaitForPage(DETECTIONS_URL); - }); - - afterEach(() => { - esArchiverUnload('alerts'); - removeSignalsIndex(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + createCustomRuleActivated(newRule); + refreshPage(); + waitForAlertsToPopulate(); }); it('Mark one alert in progress when more than one open alerts are selected', () => { - waitForAlerts(); - waitForAlertsToBeLoaded(); - cy.get(ALERTS_COUNT) .invoke('text') .then((numberOfAlerts) => { @@ -242,8 +239,6 @@ describe('Alerts', () => { cy.get(TAKE_ACTION_POPOVER_BTN).should('not.have.attr', 'disabled'); markInProgressFirstAlert(); - cy.reload(); - goToOpenedAlerts(); waitForAlertsToBeLoaded(); const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeMarkedInProgress; diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts index fa48c0bc1abc6..4bf54963a5322 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts @@ -16,7 +16,7 @@ import { } from '../tasks/login'; import { waitForAlertsIndexToBeCreated } from '../tasks/alerts'; import { goToRuleDetails } from '../tasks/alerts_detection_rules'; -import { createCustomRule, deleteCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createCustomRule, deleteCustomRule } from '../tasks/api_calls/rules'; import { getCallOut, waitForCallOutToBeShown, dismissCallOut } from '../tasks/common/callouts'; import { cleanKibana } from '../tasks/common'; @@ -42,7 +42,6 @@ describe('Detections > Callouts indicating read-only access to resources', () => // First, we have to open the app on behalf of a priviledged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. cleanKibana(); - removeSignalsIndex(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.platform_engineer); waitForAlertsIndexToBeCreated(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts index 265f4d43c71c1..44519adc25552 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts @@ -16,7 +16,7 @@ import { goToOpenedAlerts, waitForAlertsIndexToBeCreated, } from '../tasks/alerts'; -import { createCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createCustomRule } from '../tasks/api_calls/rules'; import { goToRuleDetails } from '../tasks/alerts_detection_rules'; import { waitForAlertsToPopulate } from '../tasks/create_new_rule'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; @@ -35,11 +35,10 @@ import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; import { cleanKibana } from '../tasks/common'; -describe.skip('Exceptions', () => { +describe('Exceptions', () => { const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1'; beforeEach(() => { cleanKibana(); - removeSignalsIndex(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsIndexToBeCreated(); createCustomRule(newRule); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts index 4284b05205c69..9eb2127acb446 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts @@ -4,13 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import { - FIFTH_RULE, FIRST_RULE, RULE_NAME, RULE_SWITCH, SECOND_RULE, - SEVENTH_RULE, RULE_AUTO_REFRESH_IDLE_MODAL, + FOURTH_RULE, } from '../screens/alerts_detection_rules'; import { @@ -28,43 +27,45 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRuleToBeActivated, } from '../tasks/alerts_detection_rules'; -import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../common/constants'; import { DETECTIONS_URL } from '../urls/navigation'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; import { cleanKibana } from '../tasks/common'; +import { existingRule, newOverrideRule, newRule, newThresholdRule } from '../objects/rule'; describe('Alerts detection rules', () => { - before(() => { + beforeEach(() => { cleanKibana(); removeSignalsIndex(); - esArchiverLoad('prebuilt_rules_loaded'); + loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + createCustomRule(newRule, 'rule1'); + createCustomRule(existingRule, 'rule2'); + createCustomRule(newOverrideRule, 'rule3'); + createCustomRule(newThresholdRule, 'rule4'); }); after(() => { - esArchiverUnload('prebuilt_rules_loaded'); + cy.clock().invoke('restore'); }); it('Sorts by activated rules', () => { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); - waitForAlertsPanelToBeLoaded(); - waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); - waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); cy.get(RULE_NAME) - .eq(FIFTH_RULE) + .eq(SECOND_RULE) .invoke('text') - .then((fifthRuleName) => { - activateRule(FIFTH_RULE); + .then((secondInitialRuleName) => { + activateRule(SECOND_RULE); waitForRuleToBeActivated(); cy.get(RULE_NAME) - .eq(SEVENTH_RULE) + .eq(FOURTH_RULE) .invoke('text') - .then((seventhRuleName) => { - activateRule(SEVENTH_RULE); + .then((fourthInitialRuleName) => { + activateRule(FOURTH_RULE); waitForRuleToBeActivated(); sortByActivatedRules(); cy.get(RULE_NAME) @@ -76,8 +77,8 @@ describe('Alerts detection rules', () => { .invoke('text') .then((secondRuleName) => { const expectedRulesNames = `${firstRuleName} ${secondRuleName}`; - cy.wrap(expectedRulesNames).should('include', fifthRuleName); - cy.wrap(expectedRulesNames).should('include', seventhRuleName); + cy.wrap(expectedRulesNames).should('include', secondInitialRuleName); + cy.wrap(expectedRulesNames).should('include', fourthInitialRuleName); }); }); cy.get(RULE_SWITCH).eq(FIRST_RULE).should('have.attr', 'role', 'switch'); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index fb196fde3ae83..d0b0862034a3b 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -5,7 +5,7 @@ */ import { formatMitreAttackDescription } from '../helpers/rules'; -import { newRule, existingRule, indexPatterns, editedRule } from '../objects/rule'; +import { newRule, existingRule, indexPatterns, editedRule, newOverrideRule } from '../objects/rule'; import { ALERT_RULE_METHOD, ALERT_RULE_NAME, @@ -84,7 +84,7 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createCustomRuleActivated } from '../tasks/api_calls/rules'; import { createTimeline } from '../tasks/api_calls/timelines'; import { cleanKibana } from '../tasks/common'; import { @@ -100,8 +100,8 @@ import { waitForTheRuleToBeExecuted, } from '../tasks/create_new_rule'; import { saveEditedRule, waitForKibana } from '../tasks/edit_rule'; -import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; +import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; @@ -114,9 +114,8 @@ describe('Custom detection rules creation', () => { const rule = { ...newRule }; - before(() => { + beforeEach(() => { cleanKibana(); - removeSignalsIndex(); createTimeline(newRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); @@ -213,22 +212,20 @@ describe('Custom detection rules creation', () => { }); }); -describe.skip('Custom detection rules deletion and edition', () => { - beforeEach(() => { - cleanKibana(); - removeSignalsIndex(); - esArchiverLoad('custom_rules'); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); - waitForAlertsPanelToBeLoaded(); - waitForAlertsIndexToBeCreated(); - goToManageAlertsDetectionRules(); - }); - - afterEach(() => { - esArchiverUnload('custom_rules'); - }); - +describe('Custom detection rules deletion and edition', () => { context('Deletion', () => { + beforeEach(() => { + cleanKibana(); + loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + goToManageAlertsDetectionRules(); + waitForAlertsIndexToBeCreated(); + createCustomRuleActivated(newRule, 'rule1'); + createCustomRuleActivated(newOverrideRule, 'rule2'); + createCustomRuleActivated(existingRule, 'rule3'); + refreshPage(); + goToManageAlertsDetectionRules(); + }); + it('Deletes one rule', () => { cy.get(RULES_TABLE) .find(RULES_ROW) @@ -263,7 +260,7 @@ describe.skip('Custom detection rules deletion and edition', () => { .find(RULES_ROW) .then((rules) => { const initialNumberOfRules = rules.length; - const numberOfRulesToBeDeleted = 3; + const numberOfRulesToBeDeleted = 2; const expectedNumberOfRulesAfterDeletion = initialNumberOfRules - numberOfRulesToBeDeleted; @@ -294,6 +291,16 @@ describe.skip('Custom detection rules deletion and edition', () => { const expectedEditedIndexPatterns = editedRule.index && editedRule.index.length ? editedRule.index : indexPatterns; + beforeEach(() => { + cleanKibana(); + loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + goToManageAlertsDetectionRules(); + waitForAlertsIndexToBeCreated(); + createCustomRuleActivated(existingRule, 'rule1'); + refreshPage(); + goToManageAlertsDetectionRules(); + }); + it('Allows a rule to be edited', () => { editFirstRule(); waitForKibana(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts index d02c015a5f1f7..6567ee07c4e3a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts @@ -62,7 +62,6 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; import { createTimeline } from '../tasks/api_calls/timelines'; import { cleanKibana } from '../tasks/common'; import { @@ -88,9 +87,8 @@ describe.skip('Detection rules, EQL', () => { const rule = { ...eqlRule }; - before(() => { + beforeEach(() => { cleanKibana(); - removeSignalsIndex(); createTimeline(eqlRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); @@ -180,9 +178,8 @@ describe.skip('Detection rules, sequence EQL', () => { const expectedNumberOfSequenceAlerts = 1; const rule = { ...eqlSequenceRule }; - before(() => { + beforeEach(() => { cleanKibana(); - removeSignalsIndex(); createTimeline(eqlSequenceRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts index a9c1f7c331d0e..0f5ce9c47a439 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts @@ -11,7 +11,7 @@ import { waitForAlertsPanelToBeLoaded, } from '../tasks/alerts'; import { exportFirstRule } from '../tasks/alerts_detection_rules'; -import { createCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createCustomRule } from '../tasks/api_calls/rules'; import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; @@ -19,9 +19,8 @@ import { DETECTIONS_URL } from '../urls/navigation'; describe.skip('Export rules', () => { let ruleResponse: Cypress.Response; - before(() => { + beforeEach(() => { cleanKibana(); - removeSignalsIndex(); cy.intercept( 'POST', '/api/detection_engine/rules/_export?exclude_export_details=false&file_name=rules_export.ndjson' diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts index 4e97b619fc274..1f2793abcbf1f 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts @@ -65,7 +65,6 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, @@ -91,7 +90,6 @@ describe('Detection rules, Indicator Match', () => { beforeEach(() => { cleanKibana(); - removeSignalsIndex(); esArchiverLoad('threat_indicator'); esArchiverLoad('threat_data'); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts index c651139248e0c..baefcba945447 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts @@ -52,7 +52,6 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, @@ -72,9 +71,8 @@ describe.skip('Detection rules, machine learning', () => { const expectedMitre = formatMitreAttackDescription(machineLearningRule.mitre); const expectedNumberOfRules = 1; - before(() => { + beforeEach(() => { cleanKibana(); - removeSignalsIndex(); }); it('Creates and activates a new ml rule', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts index a543dca00b010..c641d572f515c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts @@ -69,7 +69,6 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; import { createTimeline } from '../tasks/api_calls/timelines'; import { cleanKibana } from '../tasks/common'; import { @@ -84,9 +83,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -// FLAKY: https://github.com/elastic/kibana/issues/85671 -// FLAKY: https://github.com/elastic/kibana/issues/84020 -describe.skip('Detection rules, override', () => { +describe('Detection rules, override', () => { const expectedUrls = newOverrideRule.referenceUrls.join(''); const expectedFalsePositives = newOverrideRule.falsePositivesExamples.join(''); const expectedTags = newOverrideRule.tags.join(''); @@ -96,7 +93,6 @@ describe.skip('Detection rules, override', () => { beforeEach(() => { cleanKibana(); - removeSignalsIndex(); createTimeline(newOverrideRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts index a4e41631ea246..4d2efc47db483 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts @@ -30,20 +30,16 @@ import { waitForPrebuiltDetectionRulesToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { esArchiverLoadEmptyKibana } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; import { totalNumberOfPrebuiltRules } from '../objects/rule'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; import { cleanKibana } from '../tasks/common'; -describe.skip('Alerts rules, prebuilt rules', () => { - before(() => { +describe('Alerts rules, prebuilt rules', () => { + beforeEach(() => { cleanKibana(); - removeSignalsIndex(); - esArchiverLoadEmptyKibana(); }); it('Loads prebuilt rules', () => { @@ -84,7 +80,6 @@ describe('Deleting prebuilt rules', () => { const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; cleanKibana(); - esArchiverLoadEmptyKibana(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts index 812d0fa29f9b7..058bac6258ffc 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts @@ -64,7 +64,6 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; import { createTimeline } from '../tasks/api_calls/timelines'; import { cleanKibana } from '../tasks/common'; import { @@ -90,7 +89,6 @@ describe.skip('Detection rules, threshold', () => { beforeEach(() => { cleanKibana(); - removeSignalsIndex(); createTimeline(newThresholdRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts index d5fba65a70149..e42410f7fb38d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts @@ -4,30 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ +import { newRule } from '../objects/rule'; import { PROVIDER_BADGE } from '../screens/timeline'; -import { investigateFirstAlertInTimeline, waitForAlertsPanelToBeLoaded } from '../tasks/alerts'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { + investigateFirstAlertInTimeline, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, +} from '../tasks/alerts'; +import { createCustomRuleActivated } from '../tasks/api_calls/rules'; import { cleanKibana } from '../tasks/common'; -import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { waitForAlertsToPopulate } from '../tasks/create_new_rule'; import { loginAndWaitForPage } from '../tasks/login'; +import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; describe('Alerts timeline', () => { beforeEach(() => { cleanKibana(); - removeSignalsIndex(); - esArchiverLoad('timeline_alerts'); loginAndWaitForPage(DETECTIONS_URL); - }); - - afterEach(() => { - esArchiverUnload('timeline_alerts'); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + createCustomRuleActivated(newRule); + refreshPage(); + waitForAlertsToPopulate(); }); it('Investigate alert in default timeline', () => { - waitForAlertsPanelToBeLoaded(); investigateFirstAlertInTimeline(); cy.get(PROVIDER_BADGE) .first() diff --git a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts index d53b98b6c103d..18325401d9abc 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts @@ -51,10 +51,10 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { CASES_URL } from '../urls/navigation'; -describe.skip('Cases', () => { +describe('Cases', () => { const mycase = { ...case1 }; - before(() => { + beforeEach(() => { cleanKibana(); createTimeline(case1.timeline).then((response) => { mycase.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; diff --git a/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts index c41b79ef33653..e8fd69864cb3e 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts @@ -27,7 +27,7 @@ import { CONNECTOR_CARD_DETAILS, CONNECTOR_TITLE } from '../screens/case_details import { cleanKibana } from '../tasks/common'; describe('Cases connector incident fields', () => { - before(() => { + beforeEach(() => { cleanKibana(); cy.intercept('GET', '/api/cases/configure/connectors/_find', mockConnectorsResponse); cy.intercept('POST', `/api/actions/action/${connectorIds.jira}/_execute`, (req) => { diff --git a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts index 8bd9f5b09f2c8..9e39a210c1113 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts @@ -38,7 +38,7 @@ describe('Cases connectors', () => { ], version: 'WzEwNCwxXQ==', }; - before(() => { + beforeEach(() => { cleanKibana(); cy.intercept('POST', '/api/actions/action').as('createConnector'); cy.intercept('POST', '/api/cases/configure', (req) => { diff --git a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts index f7a19fa281bee..4e34dcac1873d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts @@ -23,7 +23,6 @@ import { openEvents } from '../tasks/hosts/main'; import { addsHostGeoCityNameToHeader, addsHostGeoCountryNameToHeader, - closeModal, dragAndDropColumn, openEventsViewerFieldsBrowser, opensInspectQueryModal, @@ -63,7 +62,7 @@ describe.skip('Events Viewer', () => { }); it('displays the `default ECS` category (by default)', () => { - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE).invoke('text').should('eq', 'default ECS'); + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE).should('have.text', 'default ECS'); }); it('displays a checked checkbox for all of the default events viewer columns that are also in the default ECS category', () => { @@ -80,11 +79,6 @@ describe.skip('Events Viewer', () => { openEvents(); }); - after(() => { - closeModal(); - cy.get(INSPECT_MODAL).should('not.exist'); - }); - it('launches the inspect query modal when the inspect button is clicked', () => { waitsForEventsToBeLoaded(); opensInspectQueryModal(); @@ -142,7 +136,7 @@ describe.skip('Events Viewer', () => { .invoke('text') .then((initialNumberOfEvents) => { kqlSearch(`${filterInput}{enter}`); - cy.get(HEADER_SUBTITLE).invoke('text').should('not.equal', initialNumberOfEvents); + cy.get(HEADER_SUBTITLE).should('not.have.text', initialNumberOfEvents); }); }); }); @@ -167,9 +161,9 @@ describe.skip('Events Viewer', () => { const expectedOrderAfterDragAndDrop = 'message@timestamp1host.nameevent.moduleevent.datasetevent.actionuser.namesource.ipdestination.ip'; - cy.get(HEADERS_GROUP).invoke('text').should('equal', originalColumnOrder); + cy.get(HEADERS_GROUP).should('have.text', originalColumnOrder); dragAndDropColumn({ column: 0, newPosition: 0 }); - cy.get(HEADERS_GROUP).invoke('text').should('equal', expectedOrderAfterDragAndDrop); + cy.get(HEADERS_GROUP).should('have.text', expectedOrderAfterDragAndDrop); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts index d99981b42d049..98cb7418a08a6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts @@ -46,7 +46,7 @@ const defaultHeaders = [ ]; describe('Fields Browser', () => { - context.skip('Fields Browser rendering', () => { + context('Fields Browser rendering', () => { before(() => { cleanKibana(); loginAndWaitForPage(HOSTS_URL); @@ -60,13 +60,14 @@ describe('Fields Browser', () => { }); it('displays the `default ECS` category (by default)', () => { - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE).invoke('text').should('eq', 'default ECS'); + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE).should('have.text', 'default ECS'); }); it('the `defaultECS` (selected) category count matches the default timeline header count', () => { - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_COUNT) - .invoke('text') - .should('eq', `${defaultHeaders.length}`); + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_COUNT).should( + 'have.text', + `${defaultHeaders.length}` + ); }); it('displays a checked checkbox for all of the default timeline columns', () => { @@ -80,7 +81,7 @@ describe('Fields Browser', () => { filterFieldsBrowser(filterInput); - cy.get(FIELDS_BROWSER_CATEGORIES_COUNT).invoke('text').should('eq', '2 categories'); + cy.get(FIELDS_BROWSER_CATEGORIES_COUNT).should('have.text', '2 categories'); }); it('displays a search results label with the expected count of fields matching the filter input', () => { @@ -94,9 +95,10 @@ describe('Fields Browser', () => { cy.get(FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT) .invoke('text') .then((systemCategoriesCount) => { - cy.get(FIELDS_BROWSER_FIELDS_COUNT) - .invoke('text') - .should('eq', `${+hostCategoriesCount + +systemCategoriesCount} fields`); + cy.get(FIELDS_BROWSER_FIELDS_COUNT).should( + 'have.text', + `${+hostCategoriesCount + +systemCategoriesCount} fields` + ); }); }); }); @@ -106,11 +108,11 @@ describe('Fields Browser', () => { filterFieldsBrowser(filterInput); - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_COUNT).invoke('text').should('eq', '4'); + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_COUNT).should('have.text', '4'); }); }); - context.skip('Editing the timeline', () => { + context('Editing the timeline', () => { before(() => { cleanKibana(); loginAndWaitForPage(HOSTS_URL); @@ -137,7 +139,7 @@ describe('Fields Browser', () => { const category = 'host'; filterFieldsBrowser(category); - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE).invoke('text').should('eq', category); + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE).should('have.text', category); }); it('adds a field to the timeline when the user clicks the checkbox', () => { @@ -151,7 +153,7 @@ describe('Fields Browser', () => { cy.get(FIELDS_BROWSER_HOST_GEO_CITY_NAME_HEADER).should('exist'); }); - it('adds a field to the timeline when the user drags and drops a field', () => { + it.skip('adds a field to the timeline when the user drags and drops a field', () => { const filterInput = 'host.geo.c'; filterFieldsBrowser(filterInput); diff --git a/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts index b441d33d34baf..aa126e2f33c90 100644 --- a/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts @@ -28,7 +28,7 @@ import { populateTimeline } from '../tasks/timeline'; import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; import { cleanKibana } from '../tasks/common'; -describe('Sourcerer', () => { +describe.skip('Sourcerer', () => { before(() => { cleanKibana(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts index 74bf4f03b0b14..bbb6f672f1112 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts @@ -12,11 +12,11 @@ import { selectCase, } from '../tasks/timeline'; import { DESCRIPTION_INPUT, ADD_COMMENT_INPUT } from '../screens/create_new_case'; -import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; -import { TIMELINE_CASE_ID } from '../objects/case'; -import { caseTimeline, timeline } from '../objects/timeline'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { case1 } from '../objects/case'; +import { timeline } from '../objects/timeline'; +import { createTimeline } from '../tasks/api_calls/timelines'; import { cleanKibana } from '../tasks/common'; +import { createCase } from '../tasks/api_calls/cases'; describe('attach timeline to case', () => { const myTimeline = { ...timeline }; @@ -29,10 +29,6 @@ describe('attach timeline to case', () => { }); }); - after(() => { - deleteTimeline(myTimeline.id!); - }); - it('attach timeline to a new case', () => { loginAndWaitForTimeline(myTimeline.id!); attachTimelineToNewCase(); @@ -62,25 +58,29 @@ describe('attach timeline to case', () => { }); context('with cases created', () => { + let timelineId: string; + let caseId: string; before(() => { cleanKibana(); - esArchiverLoad('case_and_timeline'); + createTimeline(timeline).then((response) => { + timelineId = response.body.data.persistTimeline.timeline.savedObjectId; + }); + createCase(case1).then((response) => { + caseId = response.body.id; + }); }); it('attach timeline to an existing case', () => { - loginAndWaitForTimeline(caseTimeline.id!); + loginAndWaitForTimeline(timelineId); attachTimelineToExistingCase(); - selectCase(TIMELINE_CASE_ID); + selectCase(caseId); cy.location('origin').then((origin) => { cy.get(ADD_COMMENT_INPUT).should( 'have.text', - `[${ - caseTimeline.title - }](${origin}/app/security/timelines?timeline=(id:%27${caseTimeline.id!}%27,isOpen:!t))` + `[${timeline.title}](${origin}/app/security/timelines?timeline=(id:%27${timelineId}%27,isOpen:!t))` ); }); - esArchiverUnload('case_and_timeline'); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts index 5d44c057c7383..a926a5ac4938a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts @@ -9,9 +9,9 @@ import { FAVORITE_TIMELINE, LOCKED_ICON, NOTES_TAB_BUTTON, + NOTES_TEXT, // NOTES_COUNT, NOTES_TEXT_AREA, - NOTE_CONTENT, PIN_EVENT, TIMELINE_DESCRIPTION, TIMELINE_FILTER, @@ -25,7 +25,6 @@ import { TIMELINES_NOTES_COUNT, TIMELINES_FAVORITE, } from '../screens/timelines'; -import { getTimelineById } from '../tasks/api_calls/timelines'; import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; @@ -47,11 +46,10 @@ import { openTimeline } from '../tasks/timelines'; import { OVERVIEW_URL } from '../urls/navigation'; -// FLAKY: https://github.com/elastic/kibana/issues/79389 -describe.skip('Timelines', () => { +describe('Timelines', () => { let timelineId: string; - before(() => { + beforeEach(() => { cleanKibana(); }); @@ -98,15 +96,10 @@ describe.skip('Timelines', () => { cy.get(PIN_EVENT) .should('have.attr', 'aria-label') .and('match', /Unpin the event in row 2/); - cy.get(LOCKED_ICON).should('be.visible'); cy.get(NOTES_TAB_BUTTON).click(); cy.get(NOTES_TEXT_AREA).should('exist'); - getTimelineById(timelineId).then((singleTimeline) => { - const noteId = singleTimeline!.body.data.getOneTimeline.notes[0].noteId; - - cy.get(NOTE_CONTENT(noteId)).should('have.text', timeline.notes); - }); + cy.get(NOTES_TEXT).should('have.text', timeline.notes); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts index 8b84ae7815452..1d0256dbfbdc9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts @@ -15,7 +15,7 @@ import { removeColumn } from '../tasks/timeline'; // Failing: See https://github.com/elastic/kibana/issues/75794 describe.skip('persistent timeline', () => { - before(() => { + beforeEach(() => { cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts index f1aaa4ab8b980..5672a232e0485 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts @@ -44,7 +44,7 @@ import { openTimeline } from '../tasks/timelines'; import { OVERVIEW_URL } from '../urls/navigation'; describe('Timeline Templates', () => { - before(() => { + beforeEach(() => { cleanKibana(); cy.intercept('PATCH', '/api/timeline').as('timeline'); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts index 015c0fc80e292..f2af37c939d02 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts @@ -19,7 +19,7 @@ describe('Export timelines', () => { let templateResponse: Cypress.Response; let templateId: string; - before(() => { + beforeEach(() => { cleanKibana(); cy.intercept('POST', 'api/timeline/_export?file_name=timelines_export.ndjson').as('export'); createTimelineTemplate(timelineTemplate).then((response) => { diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts index 9a03936c3683f..705aff7b14c6c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts @@ -4,14 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { timeline } from '../objects/timeline'; import { ID_HEADER_FIELD, ID_TOGGLE_FIELD, TIMESTAMP_HEADER_FIELD, TIMESTAMP_TOGGLE_FIELD, } from '../screens/timeline'; -import { createTimeline } from '../tasks/api_calls/timelines'; import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; @@ -28,13 +26,11 @@ import { import { HOSTS_URL } from '../urls/navigation'; -describe('toggle column in timeline', () => { +describe.skip('toggle column in timeline', () => { before(() => { cleanKibana(); cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export'); - createTimeline(timeline).then((response) => { - loginAndWaitForPage(HOSTS_URL); - }); + loginAndWaitForPage(HOSTS_URL); }); beforeEach(() => { diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts index 064d98bf01b24..a75074baeef54 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts @@ -15,7 +15,7 @@ import { cleanKibana } from '../tasks/common'; describe('Export timelines', () => { let timelineResponse: Cypress.Response; let timelineId: string; - before(() => { + beforeEach(() => { cleanKibana(); cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export'); createTimeline(timeline).then((response) => { diff --git a/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts index 58ef4cd2d96ba..cf433891ac929 100644 --- a/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts @@ -19,7 +19,7 @@ const ABSOLUTE_DATE = { startTime: '2019-08-01T20:03:29.186Z', }; -describe('URL compatibility', () => { +describe.skip('URL compatibility', () => { before(() => { cleanKibana(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts b/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts index 0b1ab12f37c91..ae0c4f35177a9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts @@ -26,7 +26,6 @@ import { exportValueList, } from '../tasks/lists'; import { VALUE_LISTS_TABLE, VALUE_LISTS_ROW, VALUE_LISTS_MODAL_ACTIVATOR } from '../screens/lists'; -import { removeSignalsIndex } from '../tasks/api_calls/rules'; import { cleanKibana } from '../tasks/common'; describe('value lists', () => { @@ -36,7 +35,6 @@ describe('value lists', () => { }); beforeEach(() => { - removeSignalsIndex(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); @@ -46,7 +44,6 @@ describe('value lists', () => { }); afterEach(() => { - removeSignalsIndex(); deleteAllValueListsFromUI(); }); diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index d48ac26472c71..c4515379eaeb2 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -176,18 +176,11 @@ export const newRule: CustomRule = { }; export const existingRule: CustomRule = { - customQuery: 'host.name:*', + customQuery: 'host.name: *', name: 'Rule 1', description: 'Description for Rule 1', - index: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - interval: '4m', + index: ['auditbeat-*'], + interval: '10s', severity: 'High', riskScore: '19', tags: ['rule1'], @@ -203,7 +196,7 @@ export const existingRule: CustomRule = { export const newOverrideRule: OverrideRule = { customQuery: 'host.name: *', index: indexPatterns, - name: 'New Rule Test', + name: 'Override Rule', description: 'The new rule description.', severity: 'High', riskScore: '17', @@ -224,7 +217,7 @@ export const newOverrideRule: OverrideRule = { export const newThresholdRule: ThresholdRule = { customQuery: 'host.name: *', index: indexPatterns, - name: 'New Rule Test', + name: 'Threshold Rule', description: 'The new rule description.', severity: 'High', riskScore: '17', diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 5ac8cd8f6cc9f..d13102620ec19 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -26,6 +26,8 @@ export const FIFTH_RULE = 4; export const FIRST_RULE = 0; +export const FOURTH_RULE = 3; + export const LOAD_PREBUILT_RULES_BTN = '[data-test-subj="load-prebuilt-rules"]'; export const LOADING_INITIAL_PREBUILT_RULES_TABLE = diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index 17567b61ad314..9db30a174ae08 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -33,8 +33,7 @@ export const COMBO_BOX_RESULT = '.euiFilterSelectItem'; export const CREATE_AND_ACTIVATE_BTN = '[data-test-subj="create-activate"]'; -export const CUSTOM_QUERY_INPUT = - '[data-test-subj="detectionEngineStepDefineRuleQueryBar"] [data-test-subj="queryInput"]'; +export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]'; export const THREAT_MATCH_QUERY_INPUT = '[data-test-subj="detectionEngineStepDefineThreatRuleQueryBar"] [data-test-subj="queryInput"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 6f31a470dd61e..ea3c42e2650eb 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -53,7 +53,7 @@ export const LOCKED_ICON = '[data-test-subj="timeline-date-picker-lock-button"]' export const NOTES = '[data-test-subj="note-card-body"]'; -const NOTE_BY_NOTE_ID = (noteId: string) => `[data-test-subj="note-preview-${noteId}"]`; +export const NOTE_BY_NOTE_ID = (noteId: string) => `[data-test-subj="note-preview-${noteId}"]`; export const NOTE_CONTENT = (noteId: string) => `${NOTE_BY_NOTE_ID(noteId)} p`; @@ -61,6 +61,8 @@ export const NOTES_TEXT_AREA = '[data-test-subj="add-a-note"] textarea'; export const NOTES_TAB_BUTTON = '[data-test-subj="timelineTabs-notes"]'; +export const NOTES_TEXT = '.euiMarkdownFormat'; + export const NOTES_COUNT = '[data-test-subj="timeline-notes-count"]'; export const OPEN_TIMELINE_ICON = '[data-test-subj="open-timeline-button"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/cases.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/cases.ts new file mode 100644 index 0000000000000..4510ebf254ee7 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/cases.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TestCase } from '../../objects/case'; + +export const createCase = (newCase: TestCase) => + cy.request({ + method: 'POST', + url: 'api/cases', + body: { + description: newCase.description, + title: newCase.name, + tags: ['tag'], + connector: { + id: 'none', + name: 'none', + type: '.none', + fields: null, + }, + settings: { + syncAlerts: true, + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index 34fc00428d2cd..29cdf4ec2be5d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -6,12 +6,12 @@ import { CustomRule } from '../../objects/rule'; -export const createCustomRule = (rule: CustomRule) => +export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing') => cy.request({ method: 'POST', url: 'api/detection_engine/rules', body: { - rule_id: 'rule_testing', + rule_id: ruleId, risk_score: parseInt(rule.riskScore, 10), description: rule.description, interval: '10s', @@ -27,11 +27,34 @@ export const createCustomRule = (rule: CustomRule) => headers: { 'kbn-xsrf': 'cypress-creds' }, }); -export const deleteCustomRule = () => { +export const createCustomRuleActivated = (rule: CustomRule, ruleId = 'rule_testing') => + cy.request({ + method: 'POST', + url: 'api/detection_engine/rules', + body: { + rule_id: ruleId, + risk_score: parseInt(rule.riskScore, 10), + description: rule.description, + interval: '10s', + name: rule.name, + severity: rule.severity.toLocaleLowerCase(), + type: 'query', + from: 'now-17520h', + index: ['auditbeat-*'], + query: rule.customQuery, + language: 'kuery', + enabled: true, + tags: ['rule1'], + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); + +export const deleteCustomRule = (ruleId = 'rule_testing') => { cy.request({ method: 'DELETE', - url: 'api/detection_engine/rules?rule_id=rule_testing', + url: `api/detection_engine/rules?rule_id=${ruleId}`, headers: { 'kbn-xsrf': 'cypress-creds' }, + failOnStatusCode: false, }); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index fbd4c4145e8ff..b6625a76981e8 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { removeSignalsIndex } from './api_calls/rules'; import { esArchiverLoadEmptyKibana } from './es_archiver'; const primaryButton = 0; @@ -65,5 +66,23 @@ export const reload = (afterReload: () => void) => { export const cleanKibana = () => { cy.exec(`curl -XDELETE "${Cypress.env('ELASTICSEARCH_URL')}/.kibana\*" -k`); + + // We wait until the kibana indexes are deleted + cy.waitUntil(() => { + cy.wait(500); + return cy + .request(`${Cypress.env('ELASTICSEARCH_URL')}/.kibana\*`) + .then((response) => JSON.stringify(response.body) === '{}'); + }); esArchiverLoadEmptyKibana(); + + // We wait until the kibana indexes are created + cy.waitUntil(() => { + cy.wait(500); + return cy + .request(`${Cypress.env('ELASTICSEARCH_URL')}/.kibana\*`) + .then((response) => JSON.stringify(response.body) !== '{}'); + }); + + removeSignalsIndex(); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts index 401a78767ac57..3e6b0ec0afaaa 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts @@ -42,7 +42,7 @@ export const loadMoreEvents = () => { export const openEventsViewerFieldsBrowser = () => { cy.get(EVENTS_VIEWER_FIELDS_BUTTON).click({ force: true }); - cy.get(SERVER_SIDE_EVENT_COUNT).invoke('text').should('not.equal', '0'); + cy.get(SERVER_SIDE_EVENT_COUNT).should('not.have.text', '0'); cy.get(FIELDS_BROWSER_CONTAINER).should('exist'); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index fee1bc4ae6892..0361bf4b72b52 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -166,12 +166,7 @@ export const pinFirstEvent = () => { export const populateTimeline = () => { executeTimelineKQL(hostExistsQuery); - cy.get(SERVER_SIDE_EVENT_COUNT) - .invoke('text') - .then((strCount) => { - const intCount = +strCount; - cy.wrap(intCount).should('be.above', 0); - }); + cy.get(SERVER_SIDE_EVENT_COUNT).should('not.have.text', '0'); }; export const unpinFirstEvent = () => { diff --git a/x-pack/test/security_solution_cypress/es_archives/alerts/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/alerts/data.json.gz deleted file mode 100644 index c0d7fb18bbdb2cecdd96b293730dcef7dbd0aa0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58602 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

    <@<3bF$Vx!=P?#9{ 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 diff --git a/x-pack/test/security_solution_cypress/es_archives/alerts/mappings.json b/x-pack/test/security_solution_cypress/es_archives/alerts/mappings.json deleted file mode 100644 index 00a2f6fb8c8df..0000000000000 --- a/x-pack/test/security_solution_cypress/es_archives/alerts/mappings.json +++ /dev/null @@ -1,8124 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".siem-signals-default": { - "is_write_index": true - } - }, - "index": ".siem-signals-default-000001", - "mappings": { - "dynamic": "false", - "_meta": { - "version": 3 - }, - "properties": { - "@timestamp": { - "type": "date" - }, - "agent": { - "properties": { - "ephemeral_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "client": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "cloud": { - "properties": { - "account": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "availability_zone": { - "type": "keyword", - "ignore_above": 1024 - }, - "instance": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "machine": { - "properties": { - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "provider": { - "type": "keyword", - "ignore_above": 1024 - }, - "region": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "container": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "image": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "tag": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "labels": { - "type": "object" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "runtime": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "destination": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "dll": { - "properties": { - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "dns": { - "properties": { - "answers": { - "properties": { - "class": { - "type": "keyword", - "ignore_above": 1024 - }, - "data": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "ttl": { - "type": "long" - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "header_flags": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "op_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "question": { - "properties": { - "class": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "subdomain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "resolved_ip": { - "type": "ip" - }, - "response_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ecs": { - "properties": { - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "error": { - "properties": { - "code": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "message": { - "type": "text", - "norms": false - }, - "stack_trace": { - "type": "keyword", - "index": false, - "doc_values": false, - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "event": { - "properties": { - "action": { - "type": "keyword", - "ignore_above": 1024 - }, - "category": { - "type": "keyword", - "ignore_above": 1024 - }, - "code": { - "type": "keyword", - "ignore_above": 1024 - }, - "created": { - "type": "date" - }, - "dataset": { - "type": "keyword", - "ignore_above": 1024 - }, - "duration": { - "type": "long" - }, - "end": { - "type": "date" - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "ingested": { - "type": "date" - }, - "kind": { - "type": "keyword", - "ignore_above": 1024 - }, - "module": { - "type": "keyword", - "ignore_above": 1024 - }, - "original": { - "type": "keyword", - "index": false, - "doc_values": false, - "ignore_above": 1024 - }, - "outcome": { - "type": "keyword", - "ignore_above": 1024 - }, - "provider": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "risk_score": { - "type": "float" - }, - "risk_score_norm": { - "type": "float" - }, - "sequence": { - "type": "long" - }, - "severity": { - "type": "long" - }, - "start": { - "type": "date" - }, - "timezone": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "url": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "file": { - "properties": { - "accessed": { - "type": "date" - }, - "attributes": { - "type": "keyword", - "ignore_above": 1024 - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "created": { - "type": "date" - }, - "ctime": { - "type": "date" - }, - "device": { - "type": "keyword", - "ignore_above": 1024 - }, - "directory": { - "type": "keyword", - "ignore_above": 1024 - }, - "drive_letter": { - "type": "keyword", - "ignore_above": 1 - }, - "extension": { - "type": "keyword", - "ignore_above": 1024 - }, - "gid": { - "type": "keyword", - "ignore_above": 1024 - }, - "group": { - "type": "keyword", - "ignore_above": 1024 - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "inode": { - "type": "keyword", - "ignore_above": 1024 - }, - "mime_type": { - "type": "keyword", - "ignore_above": 1024 - }, - "mode": { - "type": "keyword", - "ignore_above": 1024 - }, - "mtime": { - "type": "date" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "owner": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "size": { - "type": "long" - }, - "target_path": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "uid": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword", - "ignore_above": 1024 - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hostname": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "uptime": { - "type": "long" - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "http": { - "properties": { - "request": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "method": { - "type": "keyword", - "ignore_above": 1024 - }, - "referrer": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "response": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "status_code": { - "type": "long" - } - } - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "interface": { - "properties": { - "alias": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "labels": { - "type": "object" - }, - "log": { - "properties": { - "level": { - "type": "keyword", - "ignore_above": 1024 - }, - "logger": { - "type": "keyword", - "ignore_above": 1024 - }, - "origin": { - "properties": { - "file": { - "properties": { - "line": { - "type": "integer" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "function": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "original": { - "type": "keyword", - "index": false, - "doc_values": false, - "ignore_above": 1024 - }, - "syslog": { - "properties": { - "facility": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "priority": { - "type": "long" - }, - "severity": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - } - } - }, - "message": { - "type": "text", - "norms": false - }, - "network": { - "properties": { - "application": { - "type": "keyword", - "ignore_above": 1024 - }, - "bytes": { - "type": "long" - }, - "community_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "direction": { - "type": "keyword", - "ignore_above": 1024 - }, - "forwarded_ip": { - "type": "ip" - }, - "iana_number": { - "type": "keyword", - "ignore_above": 1024 - }, - "inner": { - "properties": { - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "packets": { - "type": "long" - }, - "protocol": { - "type": "keyword", - "ignore_above": 1024 - }, - "transport": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "observer": { - "properties": { - "egress": { - "properties": { - "interface": { - "properties": { - "alias": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "zone": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hostname": { - "type": "keyword", - "ignore_above": 1024 - }, - "ingress": { - "properties": { - "interface": { - "properties": { - "alias": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "zone": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - }, - "serial_number": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "vendor": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "organization": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "package": { - "properties": { - "architecture": { - "type": "keyword", - "ignore_above": 1024 - }, - "build_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "checksum": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "install_scope": { - "type": "keyword", - "ignore_above": 1024 - }, - "installed": { - "type": "date" - }, - "license": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "size": { - "type": "long" - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "process": { - "properties": { - "args": { - "type": "keyword", - "ignore_above": 1024 - }, - "args_count": { - "type": "long" - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "command_line": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "entity_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "executable": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "parent": { - "properties": { - "args": { - "type": "keyword", - "ignore_above": 1024 - }, - "args_count": { - "type": "long" - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "command_line": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "entity_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "executable": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "title": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "title": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "registry": { - "properties": { - "data": { - "properties": { - "bytes": { - "type": "keyword", - "ignore_above": 1024 - }, - "strings": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hive": { - "type": "keyword", - "ignore_above": 1024 - }, - "key": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "value": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "related": { - "properties": { - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "ip": { - "type": "ip" - }, - "user": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "rule": { - "properties": { - "author": { - "type": "keyword", - "ignore_above": 1024 - }, - "category": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "license": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "ruleset": { - "type": "keyword", - "ignore_above": 1024 - }, - "uuid": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "server": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "service": { - "properties": { - "ephemeral_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "node": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "state": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "signal": { - "properties": { - "ancestors": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "depth": { - "type": "integer" - }, - "group": { - "properties": { - "id": { - "type": "keyword" - }, - "index": { - "type": "integer" - } - } - }, - "original_event": { - "properties": { - "action": { - "type": "keyword" - }, - "category": { - "type": "keyword" - }, - "code": { - "type": "keyword" - }, - "created": { - "type": "date" - }, - "dataset": { - "type": "keyword" - }, - "duration": { - "type": "long" - }, - "end": { - "type": "date" - }, - "hash": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "kind": { - "type": "keyword" - }, - "module": { - "type": "keyword" - }, - "original": { - "type": "keyword", - "index": false, - "doc_values": false - }, - "outcome": { - "type": "keyword" - }, - "provider": { - "type": "keyword" - }, - "risk_score": { - "type": "float" - }, - "risk_score_norm": { - "type": "float" - }, - "sequence": { - "type": "long" - }, - "severity": { - "type": "long" - }, - "start": { - "type": "date" - }, - "timezone": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "original_signal": { - "type": "object", - "dynamic": "false", - "enabled": false - }, - "original_time": { - "type": "date" - }, - "parent": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "parents": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "rule": { - "properties": { - "author": { - "type": "keyword" - }, - "building_block_type": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "enabled": { - "type": "keyword" - }, - "false_positives": { - "type": "keyword" - }, - "filters": { - "type": "object" - }, - "from": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "immutable": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "interval": { - "type": "keyword" - }, - "language": { - "type": "keyword" - }, - "license": { - "type": "keyword" - }, - "max_signals": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "output_index": { - "type": "keyword" - }, - "query": { - "type": "keyword" - }, - "references": { - "type": "keyword" - }, - "risk_score": { - "type": "float" - }, - "risk_score_mapping": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "value": { - "type": "keyword" - } - } - }, - "rule_id": { - "type": "keyword" - }, - "rule_name_override": { - "type": "keyword" - }, - "saved_id": { - "type": "keyword" - }, - "severity": { - "type": "keyword" - }, - "severity_mapping": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "severity": { - "type": "keyword" - }, - "value": { - "type": "keyword" - } - } - }, - "size": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "threat": { - "properties": { - "framework": { - "type": "keyword" - }, - "tactic": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "reference": { - "type": "keyword" - } - } - }, - "technique": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "reference": { - "type": "keyword" - } - } - } - } - }, - "threshold": { - "properties": { - "field": { - "type": "keyword" - }, - "value": { - "type": "float" - } - } - }, - "timeline_id": { - "type": "keyword" - }, - "timeline_title": { - "type": "keyword" - }, - "timestamp_override": { - "type": "keyword" - }, - "to": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "status": { - "type": "keyword" - }, - "threshold_count": { - "type": "float" - }, - "threshold_result": { - "properties": { - "count": { - "type": "long" - }, - "value": { - "type": "keyword" - } - } - } - } - }, - "source": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "tags": { - "type": "keyword", - "ignore_above": 1024 - }, - "threat": { - "properties": { - "framework": { - "type": "keyword", - "ignore_above": 1024 - }, - "tactic": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "technique": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "tls": { - "properties": { - "cipher": { - "type": "keyword", - "ignore_above": 1024 - }, - "client": { - "properties": { - "certificate": { - "type": "keyword", - "ignore_above": 1024 - }, - "certificate_chain": { - "type": "keyword", - "ignore_above": 1024 - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "issuer": { - "type": "keyword", - "ignore_above": 1024 - }, - "ja3": { - "type": "keyword", - "ignore_above": 1024 - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "server_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject": { - "type": "keyword", - "ignore_above": 1024 - }, - "supported_ciphers": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "curve": { - "type": "keyword", - "ignore_above": 1024 - }, - "established": { - "type": "boolean" - }, - "next_protocol": { - "type": "keyword", - "ignore_above": 1024 - }, - "resumed": { - "type": "boolean" - }, - "server": { - "properties": { - "certificate": { - "type": "keyword", - "ignore_above": 1024 - }, - "certificate_chain": { - "type": "keyword", - "ignore_above": 1024 - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "issuer": { - "type": "keyword", - "ignore_above": 1024 - }, - "ja3s": { - "type": "keyword", - "ignore_above": 1024 - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "subject": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - }, - "version_protocol": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "trace": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "transaction": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "url": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "extension": { - "type": "keyword", - "ignore_above": 1024 - }, - "fragment": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "original": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "password": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "port": { - "type": "long" - }, - "query": { - "type": "keyword", - "ignore_above": 1024 - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "scheme": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "username": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "user_agent": { - "properties": { - "device": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "original": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vulnerability": { - "properties": { - "category": { - "type": "keyword", - "ignore_above": 1024 - }, - "classification": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "enumeration": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "report_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "scanner": { - "properties": { - "vendor": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "score": { - "properties": { - "base": { - "type": "float" - }, - "environmental": { - "type": "float" - }, - "temporal": { - "type": "float" - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "severity": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "settings": { - "index": { - "lifecycle": { - "name": ".siem-signals-default", - "rollover_alias": ".siem-signals-default" - }, - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - "auditbeat-7.6.0": { - "is_write_index": true - } - }, - "index": "auditbeat-7.6.0-2020.03.11-000001", - "mappings": { - "_meta": { - "beat": "auditbeat", - "version": "7.6.0" - }, - "date_detection": false, - "dynamic_templates": [ - { - "labels": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "labels.*" - } - }, - { - "container.labels": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "container.labels.*" - } - }, - { - "dns.answers": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "dns.answers.*" - } - }, - { - "log.syslog": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "log.syslog.*" - } - }, - { - "fields": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "fields.*" - } - }, - { - "docker.container.labels": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "docker.container.labels.*" - } - }, - { - "kubernetes.labels.*": { - "mapping": { - "type": "keyword" - }, - "path_match": "kubernetes.labels.*" - } - }, - { - "kubernetes.annotations.*": { - "mapping": { - "type": "keyword" - }, - "path_match": "kubernetes.annotations.*" - } - }, - { - "strings_as_keyword": { - "mapping": { - "ignore_above": 1024, - "type": "keyword" - }, - "match_mapping_type": "string" - } - } - ], - "properties": { - "@timestamp": { - "type": "date" - }, - "agent": { - "properties": { - "ephemeral_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "auditd": { - "properties": { - "data": { - "properties": { - "a0": { - "ignore_above": 1024, - "type": "keyword" - }, - "a1": { - "ignore_above": 1024, - "type": "keyword" - }, - "a2": { - "ignore_above": 1024, - "type": "keyword" - }, - "a3": { - "ignore_above": 1024, - "type": "keyword" - }, - "a[0-3]": { - "ignore_above": 1024, - "type": "keyword" - }, - "acct": { - "ignore_above": 1024, - "type": "keyword" - }, - "acl": { - "ignore_above": 1024, - "type": "keyword" - }, - "action": { - "ignore_above": 1024, - "type": "keyword" - }, - "added": { - "ignore_above": 1024, - "type": "keyword" - }, - "addr": { - "ignore_above": 1024, - "type": "keyword" - }, - "apparmor": { - "ignore_above": 1024, - "type": "keyword" - }, - "arch": { - "ignore_above": 1024, - "type": "keyword" - }, - "argc": { - "ignore_above": 1024, - "type": "keyword" - }, - "audit_backlog_limit": { - "ignore_above": 1024, - "type": "keyword" - }, - "audit_backlog_wait_time": { - "ignore_above": 1024, - "type": "keyword" - }, - "audit_enabled": { - "ignore_above": 1024, - "type": "keyword" - }, - "audit_failure": { - "ignore_above": 1024, - "type": "keyword" - }, - "banners": { - "ignore_above": 1024, - "type": "keyword" - }, - "bool": { - "ignore_above": 1024, - "type": "keyword" - }, - "bus": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_fe": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_fi": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_fp": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_fver": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_pe": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_pi": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_pp": { - "ignore_above": 1024, - "type": "keyword" - }, - "capability": { - "ignore_above": 1024, - "type": "keyword" - }, - "cgroup": { - "ignore_above": 1024, - "type": "keyword" - }, - "changed": { - "ignore_above": 1024, - "type": "keyword" - }, - "cipher": { - "ignore_above": 1024, - "type": "keyword" - }, - "class": { - "ignore_above": 1024, - "type": "keyword" - }, - "cmd": { - "ignore_above": 1024, - "type": "keyword" - }, - "code": { - "ignore_above": 1024, - "type": "keyword" - }, - "compat": { - "ignore_above": 1024, - "type": "keyword" - }, - "daddr": { - "ignore_above": 1024, - "type": "keyword" - }, - "data": { - "ignore_above": 1024, - "type": "keyword" - }, - "default-context": { - "ignore_above": 1024, - "type": "keyword" - }, - "device": { - "ignore_above": 1024, - "type": "keyword" - }, - "dir": { - "ignore_above": 1024, - "type": "keyword" - }, - "direction": { - "ignore_above": 1024, - "type": "keyword" - }, - "dmac": { - "ignore_above": 1024, - "type": "keyword" - }, - "dport": { - "ignore_above": 1024, - "type": "keyword" - }, - "enforcing": { - "ignore_above": 1024, - "type": "keyword" - }, - "entries": { - "ignore_above": 1024, - "type": "keyword" - }, - "exit": { - "ignore_above": 1024, - "type": "keyword" - }, - "fam": { - "ignore_above": 1024, - "type": "keyword" - }, - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "fd": { - "ignore_above": 1024, - "type": "keyword" - }, - "fe": { - "ignore_above": 1024, - "type": "keyword" - }, - "feature": { - "ignore_above": 1024, - "type": "keyword" - }, - "fi": { - "ignore_above": 1024, - "type": "keyword" - }, - "file": { - "ignore_above": 1024, - "type": "keyword" - }, - "flags": { - "ignore_above": 1024, - "type": "keyword" - }, - "format": { - "ignore_above": 1024, - "type": "keyword" - }, - "fp": { - "ignore_above": 1024, - "type": "keyword" - }, - "fver": { - "ignore_above": 1024, - "type": "keyword" - }, - "grantors": { - "ignore_above": 1024, - "type": "keyword" - }, - "grp": { - "ignore_above": 1024, - "type": "keyword" - }, - "hook": { - "ignore_above": 1024, - "type": "keyword" - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "icmp_type": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "igid": { - "ignore_above": 1024, - "type": "keyword" - }, - "img-ctx": { - "ignore_above": 1024, - "type": "keyword" - }, - "inif": { - "ignore_above": 1024, - "type": "keyword" - }, - "ino": { - "ignore_above": 1024, - "type": "keyword" - }, - "inode_gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "inode_uid": { - "ignore_above": 1024, - "type": "keyword" - }, - "invalid_context": { - "ignore_above": 1024, - "type": "keyword" - }, - "ioctlcmd": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "ignore_above": 1024, - "type": "keyword" - }, - "ipid": { - "ignore_above": 1024, - "type": "keyword" - }, - "ipx-net": { - "ignore_above": 1024, - "type": "keyword" - }, - "items": { - "ignore_above": 1024, - "type": "keyword" - }, - "iuid": { - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "kind": { - "ignore_above": 1024, - "type": "keyword" - }, - "ksize": { - "ignore_above": 1024, - "type": "keyword" - }, - "laddr": { - "ignore_above": 1024, - "type": "keyword" - }, - "len": { - "ignore_above": 1024, - "type": "keyword" - }, - "list": { - "ignore_above": 1024, - "type": "keyword" - }, - "lport": { - "ignore_above": 1024, - "type": "keyword" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "macproto": { - "ignore_above": 1024, - "type": "keyword" - }, - "maj": { - "ignore_above": 1024, - "type": "keyword" - }, - "major": { - "ignore_above": 1024, - "type": "keyword" - }, - "minor": { - "ignore_above": 1024, - "type": "keyword" - }, - "model": { - "ignore_above": 1024, - "type": "keyword" - }, - "msg": { - "ignore_above": 1024, - "type": "keyword" - }, - "nargs": { - "ignore_above": 1024, - "type": "keyword" - }, - "net": { - "ignore_above": 1024, - "type": "keyword" - }, - "new": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-chardev": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-disk": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-enabled": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-fs": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-level": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-log_passwd": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-mem": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-net": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-range": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-rng": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-role": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-seuser": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-vcpu": { - "ignore_above": 1024, - "type": "keyword" - }, - "new_gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "new_lock": { - "ignore_above": 1024, - "type": "keyword" - }, - "new_pe": { - "ignore_above": 1024, - "type": "keyword" - }, - "new_pi": { - "ignore_above": 1024, - "type": "keyword" - }, - "new_pp": { - "ignore_above": 1024, - "type": "keyword" - }, - "nlnk-fam": { - "ignore_above": 1024, - "type": "keyword" - }, - "nlnk-grp": { - "ignore_above": 1024, - "type": "keyword" - }, - "nlnk-pid": { - "ignore_above": 1024, - "type": "keyword" - }, - "oauid": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_uid": { - "ignore_above": 1024, - "type": "keyword" - }, - "ocomm": { - "ignore_above": 1024, - "type": "keyword" - }, - "oflag": { - "ignore_above": 1024, - "type": "keyword" - }, - "old": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-auid": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-chardev": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-disk": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-enabled": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-fs": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-level": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-log_passwd": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-mem": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-net": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-range": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-rng": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-role": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-ses": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-seuser": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-vcpu": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_enforcing": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_lock": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_pe": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_pi": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_pp": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_prom": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_val": { - "ignore_above": 1024, - "type": "keyword" - }, - "op": { - "ignore_above": 1024, - "type": "keyword" - }, - "opid": { - "ignore_above": 1024, - "type": "keyword" - }, - "oses": { - "ignore_above": 1024, - "type": "keyword" - }, - "outif": { - "ignore_above": 1024, - "type": "keyword" - }, - "parent": { - "ignore_above": 1024, - "type": "keyword" - }, - "per": { - "ignore_above": 1024, - "type": "keyword" - }, - "perm": { - "ignore_above": 1024, - "type": "keyword" - }, - "perm_mask": { - "ignore_above": 1024, - "type": "keyword" - }, - "permissive": { - "ignore_above": 1024, - "type": "keyword" - }, - "pfs": { - "ignore_above": 1024, - "type": "keyword" - }, - "printer": { - "ignore_above": 1024, - "type": "keyword" - }, - "prom": { - "ignore_above": 1024, - "type": "keyword" - }, - "proto": { - "ignore_above": 1024, - "type": "keyword" - }, - "qbytes": { - "ignore_above": 1024, - "type": "keyword" - }, - "range": { - "ignore_above": 1024, - "type": "keyword" - }, - "reason": { - "ignore_above": 1024, - "type": "keyword" - }, - "removed": { - "ignore_above": 1024, - "type": "keyword" - }, - "res": { - "ignore_above": 1024, - "type": "keyword" - }, - "resrc": { - "ignore_above": 1024, - "type": "keyword" - }, - "rport": { - "ignore_above": 1024, - "type": "keyword" - }, - "sauid": { - "ignore_above": 1024, - "type": "keyword" - }, - "scontext": { - "ignore_above": 1024, - "type": "keyword" - }, - "selected-context": { - "ignore_above": 1024, - "type": "keyword" - }, - "seperm": { - "ignore_above": 1024, - "type": "keyword" - }, - "seperms": { - "ignore_above": 1024, - "type": "keyword" - }, - "seqno": { - "ignore_above": 1024, - "type": "keyword" - }, - "seresult": { - "ignore_above": 1024, - "type": "keyword" - }, - "ses": { - "ignore_above": 1024, - "type": "keyword" - }, - "seuser": { - "ignore_above": 1024, - "type": "keyword" - }, - "sig": { - "ignore_above": 1024, - "type": "keyword" - }, - "sigev_signo": { - "ignore_above": 1024, - "type": "keyword" - }, - "smac": { - "ignore_above": 1024, - "type": "keyword" - }, - "socket": { - "properties": { - "addr": { - "ignore_above": 1024, - "type": "keyword" - }, - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "port": { - "ignore_above": 1024, - "type": "keyword" - }, - "saddr": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "spid": { - "ignore_above": 1024, - "type": "keyword" - }, - "sport": { - "ignore_above": 1024, - "type": "keyword" - }, - "state": { - "ignore_above": 1024, - "type": "keyword" - }, - "subj": { - "ignore_above": 1024, - "type": "keyword" - }, - "success": { - "ignore_above": 1024, - "type": "keyword" - }, - "syscall": { - "ignore_above": 1024, - "type": "keyword" - }, - "table": { - "ignore_above": 1024, - "type": "keyword" - }, - "tclass": { - "ignore_above": 1024, - "type": "keyword" - }, - "tcontext": { - "ignore_above": 1024, - "type": "keyword" - }, - "terminal": { - "ignore_above": 1024, - "type": "keyword" - }, - "tty": { - "ignore_above": 1024, - "type": "keyword" - }, - "unit": { - "ignore_above": 1024, - "type": "keyword" - }, - "uri": { - "ignore_above": 1024, - "type": "keyword" - }, - "uuid": { - "ignore_above": 1024, - "type": "keyword" - }, - "val": { - "ignore_above": 1024, - "type": "keyword" - }, - "ver": { - "ignore_above": 1024, - "type": "keyword" - }, - "virt": { - "ignore_above": 1024, - "type": "keyword" - }, - "vm": { - "ignore_above": 1024, - "type": "keyword" - }, - "vm-ctx": { - "ignore_above": 1024, - "type": "keyword" - }, - "vm-pid": { - "ignore_above": 1024, - "type": "keyword" - }, - "watch": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "message_type": { - "ignore_above": 1024, - "type": "keyword" - }, - "paths": { - "properties": { - "dev": { - "ignore_above": 1024, - "type": "keyword" - }, - "inode": { - "ignore_above": 1024, - "type": "keyword" - }, - "item": { - "ignore_above": 1024, - "type": "keyword" - }, - "mode": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "nametype": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_level": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_role": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_user": { - "ignore_above": 1024, - "type": "keyword" - }, - "objtype": { - "ignore_above": 1024, - "type": "keyword" - }, - "ogid": { - "ignore_above": 1024, - "type": "keyword" - }, - "ouid": { - "ignore_above": 1024, - "type": "keyword" - }, - "rdev": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "result": { - "ignore_above": 1024, - "type": "keyword" - }, - "sequence": { - "type": "long" - }, - "session": { - "ignore_above": 1024, - "type": "keyword" - }, - "summary": { - "properties": { - "actor": { - "properties": { - "primary": { - "ignore_above": 1024, - "type": "keyword" - }, - "secondary": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "how": { - "ignore_above": 1024, - "type": "keyword" - }, - "object": { - "properties": { - "primary": { - "ignore_above": 1024, - "type": "keyword" - }, - "secondary": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "client": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "account": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "availability_zone": { - "ignore_above": 1024, - "type": "keyword" - }, - "image": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "instance": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "machine": { - "properties": { - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "project": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "provider": { - "ignore_above": 1024, - "type": "keyword" - }, - "region": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "container": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "image": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "tag": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "labels": { - "type": "object" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "runtime": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "destination": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "dns": { - "properties": { - "answers": { - "properties": { - "class": { - "ignore_above": 1024, - "type": "keyword" - }, - "data": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "ttl": { - "type": "long" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "header_flags": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "op_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "question": { - "properties": { - "class": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "subdomain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "resolved_ip": { - "type": "ip" - }, - "response_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "docker": { - "properties": { - "container": { - "properties": { - "labels": { - "type": "object" - } - } - } - } - }, - "ecs": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "error": { - "properties": { - "code": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "message": { - "norms": false, - "type": "text" - }, - "stack_trace": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "event": { - "properties": { - "action": { - "ignore_above": 1024, - "type": "keyword" - }, - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "code": { - "ignore_above": 1024, - "type": "keyword" - }, - "created": { - "type": "date" - }, - "dataset": { - "ignore_above": 1024, - "type": "keyword" - }, - "duration": { - "type": "long" - }, - "end": { - "type": "date" - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "ingested": { - "type": "date" - }, - "kind": { - "ignore_above": 1024, - "type": "keyword" - }, - "module": { - "ignore_above": 1024, - "type": "keyword" - }, - "origin": { - "ignore_above": 1024, - "type": "keyword" - }, - "original": { - "ignore_above": 1024, - "type": "keyword" - }, - "outcome": { - "ignore_above": 1024, - "type": "keyword" - }, - "provider": { - "ignore_above": 1024, - "type": "keyword" - }, - "risk_score": { - "type": "float" - }, - "risk_score_norm": { - "type": "float" - }, - "sequence": { - "type": "long" - }, - "severity": { - "type": "long" - }, - "start": { - "type": "date" - }, - "timezone": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "fields": { - "type": "object" - }, - "file": { - "properties": { - "accessed": { - "type": "date" - }, - "attributes": { - "ignore_above": 1024, - "type": "keyword" - }, - "created": { - "type": "date" - }, - "ctime": { - "type": "date" - }, - "device": { - "ignore_above": 1024, - "type": "keyword" - }, - "directory": { - "ignore_above": 1024, - "type": "keyword" - }, - "drive_letter": { - "ignore_above": 1, - "type": "keyword" - }, - "extension": { - "ignore_above": 1024, - "type": "keyword" - }, - "gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "ignore_above": 1024, - "type": "keyword" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "inode": { - "ignore_above": 1024, - "type": "keyword" - }, - "mode": { - "ignore_above": 1024, - "type": "keyword" - }, - "mtime": { - "type": "date" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "origin": { - "fields": { - "raw": { - "ignore_above": 1024, - "type": "keyword" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "owner": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "selinux": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "level": { - "ignore_above": 1024, - "type": "keyword" - }, - "role": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "setgid": { - "type": "boolean" - }, - "setuid": { - "type": "boolean" - }, - "size": { - "type": "long" - }, - "target_path": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "uid": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "geoip": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "properties": { - "blake2b_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "blake2b_384": { - "ignore_above": 1024, - "type": "keyword" - }, - "blake2b_512": { - "ignore_above": 1024, - "type": "keyword" - }, - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha384": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_384": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_512": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512_224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "xxh64": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, - "containerized": { - "type": "boolean" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "ignore_above": 1024, - "type": "keyword" - }, - "codename": { - "ignore_above": 1024, - "type": "keyword" - }, - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "uptime": { - "type": "long" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "http": { - "properties": { - "request": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "bytes": { - "type": "long" - }, - "method": { - "ignore_above": 1024, - "type": "keyword" - }, - "referrer": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "response": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "bytes": { - "type": "long" - }, - "status_code": { - "type": "long" - } - } - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "jolokia": { - "properties": { - "agent": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "secured": { - "type": "boolean" - }, - "server": { - "properties": { - "product": { - "ignore_above": 1024, - "type": "keyword" - }, - "vendor": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "url": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "kubernetes": { - "properties": { - "annotations": { - "properties": { - "*": { - "type": "object" - } - } - }, - "container": { - "properties": { - "image": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "deployment": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "labels": { - "properties": { - "*": { - "type": "object" - } - } - }, - "namespace": { - "ignore_above": 1024, - "type": "keyword" - }, - "node": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "pod": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "uid": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "replicaset": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "statefulset": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "labels": { - "type": "object" - }, - "log": { - "properties": { - "level": { - "ignore_above": 1024, - "type": "keyword" - }, - "logger": { - "ignore_above": 1024, - "type": "keyword" - }, - "origin": { - "properties": { - "file": { - "properties": { - "line": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "function": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "original": { - "ignore_above": 1024, - "type": "keyword" - }, - "syslog": { - "properties": { - "facility": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "priority": { - "type": "long" - }, - "severity": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "message": { - "norms": false, - "type": "text" - }, - "network": { - "properties": { - "application": { - "ignore_above": 1024, - "type": "keyword" - }, - "bytes": { - "type": "long" - }, - "community_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "direction": { - "ignore_above": 1024, - "type": "keyword" - }, - "forwarded_ip": { - "type": "ip" - }, - "iana_number": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "packets": { - "type": "long" - }, - "protocol": { - "ignore_above": 1024, - "type": "keyword" - }, - "transport": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "observer": { - "properties": { - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "product": { - "ignore_above": 1024, - "type": "keyword" - }, - "serial_number": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "vendor": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "organization": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "os": { - "properties": { - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "package": { - "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, - "build_version": { - "ignore_above": 1024, - "type": "keyword" - }, - "checksum": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "install_scope": { - "ignore_above": 1024, - "type": "keyword" - }, - "installed": { - "type": "date" - }, - "license": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - }, - "size": { - "type": "long" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "process": { - "properties": { - "args": { - "ignore_above": 1024, - "type": "keyword" - }, - "args_count": { - "type": "long" - }, - "command_line": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "entity_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "executable": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "blake2b_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "blake2b_384": { - "ignore_above": 1024, - "type": "keyword" - }, - "blake2b_512": { - "ignore_above": 1024, - "type": "keyword" - }, - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha384": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_384": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_512": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512_224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "xxh64": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "parent": { - "properties": { - "args": { - "ignore_above": 1024, - "type": "keyword" - }, - "args_count": { - "type": "long" - }, - "command_line": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "executable": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "exit_code": { - "type": "long" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "title": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "title": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "registry": { - "properties": { - "data": { - "properties": { - "bytes": { - "ignore_above": 1024, - "type": "keyword" - }, - "strings": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hive": { - "ignore_above": 1024, - "type": "keyword" - }, - "key": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "value": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "related": { - "properties": { - "ip": { - "type": "ip" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "rule": { - "properties": { - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - }, - "ruleset": { - "ignore_above": 1024, - "type": "keyword" - }, - "uuid": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "server": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "service": { - "properties": { - "ephemeral_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "node": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "state": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "socket": { - "properties": { - "entity_id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "source": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "system": { - "properties": { - "audit": { - "properties": { - "host": { - "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, - "boottime": { - "type": "date" - }, - "containerized": { - "type": "boolean" - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "codename": { - "ignore_above": 1024, - "type": "keyword" - }, - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "timezone": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "offset": { - "properties": { - "sec": { - "type": "long" - } - } - } - } - }, - "uptime": { - "type": "long" - } - } - }, - "package": { - "properties": { - "arch": { - "ignore_above": 1024, - "type": "keyword" - }, - "entity_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "installtime": { - "type": "date" - }, - "license": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "release": { - "ignore_above": 1024, - "type": "keyword" - }, - "size": { - "type": "long" - }, - "summary": { - "ignore_above": 1024, - "type": "keyword" - }, - "url": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "user": { - "properties": { - "dir": { - "ignore_above": 1024, - "type": "keyword" - }, - "gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "type": "object" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "password": { - "properties": { - "last_changed": { - "type": "date" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "shell": { - "ignore_above": 1024, - "type": "keyword" - }, - "uid": { - "ignore_above": 1024, - "type": "keyword" - }, - "user_information": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "tags": { - "ignore_above": 1024, - "type": "keyword" - }, - "threat": { - "properties": { - "framework": { - "ignore_above": 1024, - "type": "keyword" - }, - "tactic": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "technique": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "timeseries": { - "properties": { - "instance": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "tls": { - "properties": { - "cipher": { - "ignore_above": 1024, - "type": "keyword" - }, - "client": { - "properties": { - "certificate": { - "ignore_above": 1024, - "type": "keyword" - }, - "certificate_chain": { - "ignore_above": 1024, - "type": "keyword" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "issuer": { - "ignore_above": 1024, - "type": "keyword" - }, - "ja3": { - "ignore_above": 1024, - "type": "keyword" - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "server_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "subject": { - "ignore_above": 1024, - "type": "keyword" - }, - "supported_ciphers": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "curve": { - "ignore_above": 1024, - "type": "keyword" - }, - "established": { - "type": "boolean" - }, - "next_protocol": { - "ignore_above": 1024, - "type": "keyword" - }, - "resumed": { - "type": "boolean" - }, - "server": { - "properties": { - "certificate": { - "ignore_above": 1024, - "type": "keyword" - }, - "certificate_chain": { - "ignore_above": 1024, - "type": "keyword" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "issuer": { - "ignore_above": 1024, - "type": "keyword" - }, - "ja3s": { - "ignore_above": 1024, - "type": "keyword" - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "subject": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - }, - "version_protocol": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "tracing": { - "properties": { - "trace": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "transaction": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "url": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "extension": { - "ignore_above": 1024, - "type": "keyword" - }, - "fragment": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "original": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "password": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "port": { - "type": "long" - }, - "query": { - "ignore_above": 1024, - "type": "keyword" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "scheme": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "username": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "user": { - "properties": { - "audit": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "effective": { - "properties": { - "group": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "entity_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "filesystem": { - "properties": { - "group": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "name_map": { - "type": "object" - }, - "saved": { - "properties": { - "group": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "selinux": { - "properties": { - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "level": { - "ignore_above": 1024, - "type": "keyword" - }, - "role": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "terminal": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "user_agent": { - "properties": { - "device": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "original": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "vulnerability": { - "properties": { - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "classification": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "enumeration": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - }, - "report_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "scanner": { - "properties": { - "vendor": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "score": { - "properties": { - "base": { - "type": "float" - }, - "environmental": { - "type": "float" - }, - "temporal": { - "type": "float" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "severity": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "settings": { - "index": { - "lifecycle": { - "name": "auditbeat", - "rollover_alias": "auditbeat-7.6.0" - }, - "mapping": { - "total_fields": { - "limit": "10000" - } - }, - "number_of_replicas": "1", - "number_of_shards": "1", - "query": { - "default_field": [ - "message", - "tags", - "agent.ephemeral_id", - "agent.id", - "agent.name", - "agent.type", - "agent.version", - "as.organization.name", - "client.address", - "client.as.organization.name", - "client.domain", - "client.geo.city_name", - "client.geo.continent_name", - "client.geo.country_iso_code", - "client.geo.country_name", - "client.geo.name", - "client.geo.region_iso_code", - "client.geo.region_name", - "client.mac", - "client.registered_domain", - "client.top_level_domain", - "client.user.domain", - "client.user.email", - "client.user.full_name", - "client.user.group.domain", - "client.user.group.id", - "client.user.group.name", - "client.user.hash", - "client.user.id", - "client.user.name", - "cloud.account.id", - "cloud.availability_zone", - "cloud.instance.id", - "cloud.instance.name", - "cloud.machine.type", - "cloud.provider", - "cloud.region", - "container.id", - "container.image.name", - "container.image.tag", - "container.name", - "container.runtime", - "destination.address", - "destination.as.organization.name", - "destination.domain", - "destination.geo.city_name", - "destination.geo.continent_name", - "destination.geo.country_iso_code", - "destination.geo.country_name", - "destination.geo.name", - "destination.geo.region_iso_code", - "destination.geo.region_name", - "destination.mac", - "destination.registered_domain", - "destination.top_level_domain", - "destination.user.domain", - "destination.user.email", - "destination.user.full_name", - "destination.user.group.domain", - "destination.user.group.id", - "destination.user.group.name", - "destination.user.hash", - "destination.user.id", - "destination.user.name", - "dns.answers.class", - "dns.answers.data", - "dns.answers.name", - "dns.answers.type", - "dns.header_flags", - "dns.id", - "dns.op_code", - "dns.question.class", - "dns.question.name", - "dns.question.registered_domain", - "dns.question.subdomain", - "dns.question.top_level_domain", - "dns.question.type", - "dns.response_code", - "dns.type", - "ecs.version", - "error.code", - "error.id", - "error.message", - "error.stack_trace", - "error.type", - "event.action", - "event.category", - "event.code", - "event.dataset", - "event.hash", - "event.id", - "event.kind", - "event.module", - "event.original", - "event.outcome", - "event.provider", - "event.timezone", - "event.type", - "file.device", - "file.directory", - "file.extension", - "file.gid", - "file.group", - "file.hash.md5", - "file.hash.sha1", - "file.hash.sha256", - "file.hash.sha512", - "file.inode", - "file.mode", - "file.name", - "file.owner", - "file.path", - "file.target_path", - "file.type", - "file.uid", - "geo.city_name", - "geo.continent_name", - "geo.country_iso_code", - "geo.country_name", - "geo.name", - "geo.region_iso_code", - "geo.region_name", - "group.domain", - "group.id", - "group.name", - "hash.md5", - "hash.sha1", - "hash.sha256", - "hash.sha512", - "host.architecture", - "host.geo.city_name", - "host.geo.continent_name", - "host.geo.country_iso_code", - "host.geo.country_name", - "host.geo.name", - "host.geo.region_iso_code", - "host.geo.region_name", - "host.hostname", - "host.id", - "host.mac", - "host.name", - "host.os.family", - "host.os.full", - "host.os.kernel", - "host.os.name", - "host.os.platform", - "host.os.version", - "host.type", - "host.user.domain", - "host.user.email", - "host.user.full_name", - "host.user.group.domain", - "host.user.group.id", - "host.user.group.name", - "host.user.hash", - "host.user.id", - "host.user.name", - "http.request.body.content", - "http.request.method", - "http.request.referrer", - "http.response.body.content", - "http.version", - "log.level", - "log.logger", - "log.origin.file.name", - "log.origin.function", - "log.original", - "log.syslog.facility.name", - "log.syslog.severity.name", - "network.application", - "network.community_id", - "network.direction", - "network.iana_number", - "network.name", - "network.protocol", - "network.transport", - "network.type", - "observer.geo.city_name", - "observer.geo.continent_name", - "observer.geo.country_iso_code", - "observer.geo.country_name", - "observer.geo.name", - "observer.geo.region_iso_code", - "observer.geo.region_name", - "observer.hostname", - "observer.mac", - "observer.name", - "observer.os.family", - "observer.os.full", - "observer.os.kernel", - "observer.os.name", - "observer.os.platform", - "observer.os.version", - "observer.product", - "observer.serial_number", - "observer.type", - "observer.vendor", - "observer.version", - "organization.id", - "organization.name", - "os.family", - "os.full", - "os.kernel", - "os.name", - "os.platform", - "os.version", - "package.architecture", - "package.checksum", - "package.description", - "package.install_scope", - "package.license", - "package.name", - "package.path", - "package.version", - "process.args", - "text", - "process.executable", - "process.hash.md5", - "process.hash.sha1", - "process.hash.sha256", - "process.hash.sha512", - "process.name", - "text", - "text", - "text", - "text", - "text", - "process.thread.name", - "process.title", - "process.working_directory", - "server.address", - "server.as.organization.name", - "server.domain", - "server.geo.city_name", - "server.geo.continent_name", - "server.geo.country_iso_code", - "server.geo.country_name", - "server.geo.name", - "server.geo.region_iso_code", - "server.geo.region_name", - "server.mac", - "server.registered_domain", - "server.top_level_domain", - "server.user.domain", - "server.user.email", - "server.user.full_name", - "server.user.group.domain", - "server.user.group.id", - "server.user.group.name", - "server.user.hash", - "server.user.id", - "server.user.name", - "service.ephemeral_id", - "service.id", - "service.name", - "service.node.name", - "service.state", - "service.type", - "service.version", - "source.address", - "source.as.organization.name", - "source.domain", - "source.geo.city_name", - "source.geo.continent_name", - "source.geo.country_iso_code", - "source.geo.country_name", - "source.geo.name", - "source.geo.region_iso_code", - "source.geo.region_name", - "source.mac", - "source.registered_domain", - "source.top_level_domain", - "source.user.domain", - "source.user.email", - "source.user.full_name", - "source.user.group.domain", - "source.user.group.id", - "source.user.group.name", - "source.user.hash", - "source.user.id", - "source.user.name", - "threat.framework", - "threat.tactic.id", - "threat.tactic.name", - "threat.tactic.reference", - "threat.technique.id", - "threat.technique.name", - "threat.technique.reference", - "tracing.trace.id", - "tracing.transaction.id", - "url.domain", - "url.extension", - "url.fragment", - "url.full", - "url.original", - "url.password", - "url.path", - "url.query", - "url.registered_domain", - "url.scheme", - "url.top_level_domain", - "url.username", - "user.domain", - "user.email", - "user.full_name", - "user.group.domain", - "user.group.id", - "user.group.name", - "user.hash", - "user.id", - "user.name", - "user_agent.device.name", - "user_agent.name", - "text", - "user_agent.original", - "user_agent.os.family", - "user_agent.os.full", - "user_agent.os.kernel", - "user_agent.os.name", - "user_agent.os.platform", - "user_agent.os.version", - "user_agent.version", - "text", - "agent.hostname", - "timeseries.instance", - "cloud.project.id", - "cloud.image.id", - "host.os.build", - "host.os.codename", - "kubernetes.pod.name", - "kubernetes.pod.uid", - "kubernetes.namespace", - "kubernetes.node.name", - "kubernetes.replicaset.name", - "kubernetes.deployment.name", - "kubernetes.statefulset.name", - "kubernetes.container.name", - "kubernetes.container.image", - "jolokia.agent.version", - "jolokia.agent.id", - "jolokia.server.product", - "jolokia.server.version", - "jolokia.server.vendor", - "jolokia.url", - "raw", - "file.origin", - "file.selinux.user", - "file.selinux.role", - "file.selinux.domain", - "file.selinux.level", - "user.audit.id", - "user.audit.name", - "user.effective.id", - "user.effective.name", - "user.effective.group.id", - "user.effective.group.name", - "user.filesystem.id", - "user.filesystem.name", - "user.filesystem.group.id", - "user.filesystem.group.name", - "user.saved.id", - "user.saved.name", - "user.saved.group.id", - "user.saved.group.name", - "user.selinux.user", - "user.selinux.role", - "user.selinux.domain", - "user.selinux.level", - "user.selinux.category", - "source.path", - "destination.path", - "auditd.message_type", - "auditd.session", - "auditd.result", - "auditd.summary.actor.primary", - "auditd.summary.actor.secondary", - "auditd.summary.object.type", - "auditd.summary.object.primary", - "auditd.summary.object.secondary", - "auditd.summary.how", - "auditd.paths.inode", - "auditd.paths.dev", - "auditd.paths.obj_user", - "auditd.paths.obj_role", - "auditd.paths.obj_domain", - "auditd.paths.obj_level", - "auditd.paths.objtype", - "auditd.paths.ouid", - "auditd.paths.rdev", - "auditd.paths.nametype", - "auditd.paths.ogid", - "auditd.paths.item", - "auditd.paths.mode", - "auditd.paths.name", - "auditd.data.action", - "auditd.data.minor", - "auditd.data.acct", - "auditd.data.addr", - "auditd.data.cipher", - "auditd.data.id", - "auditd.data.entries", - "auditd.data.kind", - "auditd.data.ksize", - "auditd.data.spid", - "auditd.data.arch", - "auditd.data.argc", - "auditd.data.major", - "auditd.data.unit", - "auditd.data.table", - "auditd.data.terminal", - "auditd.data.grantors", - "auditd.data.direction", - "auditd.data.op", - "auditd.data.tty", - "auditd.data.syscall", - "auditd.data.data", - "auditd.data.family", - "auditd.data.mac", - "auditd.data.pfs", - "auditd.data.items", - "auditd.data.a0", - "auditd.data.a1", - "auditd.data.a2", - "auditd.data.a3", - "auditd.data.hostname", - "auditd.data.lport", - "auditd.data.rport", - "auditd.data.exit", - "auditd.data.fp", - "auditd.data.laddr", - "auditd.data.sport", - "auditd.data.capability", - "auditd.data.nargs", - "auditd.data.new-enabled", - "auditd.data.audit_backlog_limit", - "auditd.data.dir", - "auditd.data.cap_pe", - "auditd.data.model", - "auditd.data.new_pp", - "auditd.data.old-enabled", - "auditd.data.oauid", - "auditd.data.old", - "auditd.data.banners", - "auditd.data.feature", - "auditd.data.vm-ctx", - "auditd.data.opid", - "auditd.data.seperms", - "auditd.data.seresult", - "auditd.data.new-rng", - "auditd.data.old-net", - "auditd.data.sigev_signo", - "auditd.data.ino", - "auditd.data.old_enforcing", - "auditd.data.old-vcpu", - "auditd.data.range", - "auditd.data.res", - "auditd.data.added", - "auditd.data.fam", - "auditd.data.nlnk-pid", - "auditd.data.subj", - "auditd.data.a[0-3]", - "auditd.data.cgroup", - "auditd.data.kernel", - "auditd.data.ocomm", - "auditd.data.new-net", - "auditd.data.permissive", - "auditd.data.class", - "auditd.data.compat", - "auditd.data.fi", - "auditd.data.changed", - "auditd.data.msg", - "auditd.data.dport", - "auditd.data.new-seuser", - "auditd.data.invalid_context", - "auditd.data.dmac", - "auditd.data.ipx-net", - "auditd.data.iuid", - "auditd.data.macproto", - "auditd.data.obj", - "auditd.data.ipid", - "auditd.data.new-fs", - "auditd.data.vm-pid", - "auditd.data.cap_pi", - "auditd.data.old-auid", - "auditd.data.oses", - "auditd.data.fd", - "auditd.data.igid", - "auditd.data.new-disk", - "auditd.data.parent", - "auditd.data.len", - "auditd.data.oflag", - "auditd.data.uuid", - "auditd.data.code", - "auditd.data.nlnk-grp", - "auditd.data.cap_fp", - "auditd.data.new-mem", - "auditd.data.seperm", - "auditd.data.enforcing", - "auditd.data.new-chardev", - "auditd.data.old-rng", - "auditd.data.outif", - "auditd.data.cmd", - "auditd.data.hook", - "auditd.data.new-level", - "auditd.data.sauid", - "auditd.data.sig", - "auditd.data.audit_backlog_wait_time", - "auditd.data.printer", - "auditd.data.old-mem", - "auditd.data.perm", - "auditd.data.old_pi", - "auditd.data.state", - "auditd.data.format", - "auditd.data.new_gid", - "auditd.data.tcontext", - "auditd.data.maj", - "auditd.data.watch", - "auditd.data.device", - "auditd.data.grp", - "auditd.data.bool", - "auditd.data.icmp_type", - "auditd.data.new_lock", - "auditd.data.old_prom", - "auditd.data.acl", - "auditd.data.ip", - "auditd.data.new_pi", - "auditd.data.default-context", - "auditd.data.inode_gid", - "auditd.data.new-log_passwd", - "auditd.data.new_pe", - "auditd.data.selected-context", - "auditd.data.cap_fver", - "auditd.data.file", - "auditd.data.net", - "auditd.data.virt", - "auditd.data.cap_pp", - "auditd.data.old-range", - "auditd.data.resrc", - "auditd.data.new-range", - "auditd.data.obj_gid", - "auditd.data.proto", - "auditd.data.old-disk", - "auditd.data.audit_failure", - "auditd.data.inif", - "auditd.data.vm", - "auditd.data.flags", - "auditd.data.nlnk-fam", - "auditd.data.old-fs", - "auditd.data.old-ses", - "auditd.data.seqno", - "auditd.data.fver", - "auditd.data.qbytes", - "auditd.data.seuser", - "auditd.data.cap_fe", - "auditd.data.new-vcpu", - "auditd.data.old-level", - "auditd.data.old_pp", - "auditd.data.daddr", - "auditd.data.old-role", - "auditd.data.ioctlcmd", - "auditd.data.smac", - "auditd.data.apparmor", - "auditd.data.fe", - "auditd.data.perm_mask", - "auditd.data.ses", - "auditd.data.cap_fi", - "auditd.data.obj_uid", - "auditd.data.reason", - "auditd.data.list", - "auditd.data.old_lock", - "auditd.data.bus", - "auditd.data.old_pe", - "auditd.data.new-role", - "auditd.data.prom", - "auditd.data.uri", - "auditd.data.audit_enabled", - "auditd.data.old-log_passwd", - "auditd.data.old-seuser", - "auditd.data.per", - "auditd.data.scontext", - "auditd.data.tclass", - "auditd.data.ver", - "auditd.data.new", - "auditd.data.val", - "auditd.data.img-ctx", - "auditd.data.old-chardev", - "auditd.data.old_val", - "auditd.data.success", - "auditd.data.inode_uid", - "auditd.data.removed", - "auditd.data.socket.port", - "auditd.data.socket.saddr", - "auditd.data.socket.addr", - "auditd.data.socket.family", - "auditd.data.socket.path", - "geoip.continent_name", - "geoip.city_name", - "geoip.region_name", - "geoip.country_iso_code", - "hash.blake2b_256", - "hash.blake2b_384", - "hash.blake2b_512", - "hash.md5", - "hash.sha1", - "hash.sha224", - "hash.sha256", - "hash.sha384", - "hash.sha3_224", - "hash.sha3_256", - "hash.sha3_384", - "hash.sha3_512", - "hash.sha512", - "hash.sha512_224", - "hash.sha512_256", - "hash.xxh64", - "event.origin", - "user.entity_id", - "user.terminal", - "process.entity_id", - "process.hash.blake2b_256", - "process.hash.blake2b_384", - "process.hash.blake2b_512", - "process.hash.sha224", - "process.hash.sha384", - "process.hash.sha3_224", - "process.hash.sha3_256", - "process.hash.sha3_384", - "process.hash.sha3_512", - "process.hash.sha512_224", - "process.hash.sha512_256", - "process.hash.xxh64", - "socket.entity_id", - "system.audit.host.timezone.name", - "system.audit.host.hostname", - "system.audit.host.id", - "system.audit.host.architecture", - "system.audit.host.mac", - "system.audit.host.os.codename", - "system.audit.host.os.platform", - "system.audit.host.os.name", - "system.audit.host.os.family", - "system.audit.host.os.version", - "system.audit.host.os.kernel", - "system.audit.package.entity_id", - "system.audit.package.name", - "system.audit.package.version", - "system.audit.package.release", - "system.audit.package.arch", - "system.audit.package.license", - "system.audit.package.summary", - "system.audit.package.url", - "system.audit.user.name", - "system.audit.user.uid", - "system.audit.user.gid", - "system.audit.user.dir", - "system.audit.user.shell", - "system.audit.user.user_information", - "system.audit.user.password.type", - "fields.*" - ] - }, - "refresh_interval": "5s" - } - } - } -} diff --git a/x-pack/test/security_solution_cypress/es_archives/case_and_timeline/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/case_and_timeline/data.json.gz deleted file mode 100644 index 5838d18e1c7dd2c5907cc5f5ef9f55d4cfe2a8d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3687 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 z 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 ahRbA diff --git a/x-pack/test/security_solution_cypress/es_archives/custom_rules/mappings.json b/x-pack/test/security_solution_cypress/es_archives/custom_rules/mappings.json deleted file mode 100644 index a6b171cdfd7d9..0000000000000 --- a/x-pack/test/security_solution_cypress/es_archives/custom_rules/mappings.json +++ /dev/null @@ -1,6243 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "action": "6e96ac5e648f57523879661ea72525b7", - "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", - "agent_configs": "38abaf89513877745c359e7700c0c66a", - "agent_events": "3231653fafe4ef3196fe3b32ab774bf2", - "agents": "75c0f4a11560dbc38b65e5e1d98fc9da", - "alert": "7b44fba6773e37c806ce290ea9b7024e", - "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", - "apm-telemetry": "e8619030e08b671291af04c4603b4944", - "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", - "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", - "canvas-element": "7390014e1091044523666d97247392fc", - "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "cases": "08b8b110dbca273d37e8aef131ecab61", - "cases-comments": "df3c1aa1b3dd5737c94d9e430b13c48a", - "cases-configure": "42711cbb311976c0687853f4c1354572", - "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "config": "ae24d22d5986d04124cc6568f771066f", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "datasources": "d4bc0c252b2b5683ff21ea32d00acffc", - "enrollment_api_keys": "28b91e20b105b6f928e2012600085d8f", - "epm-package": "75d12cd13c867fd713d7dfb27366bc20", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", - "inventory-view": "9ecce5b58867403613d82fe496470b34", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "lens": "21c3ea0763beb1ecb0162529706b88c5", - "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", - "map": "23d7aa4a720d4938ccde3983f87bd58d", - "maps-telemetry": "268da3a48066123fc5baf35abaa55014", - "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "namespace": "2f4316de49999235636386fe51dc06c1", - "outputs": "aee9782e0d500b867859650a36280165", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "181661168bbadd1eff5902361e2a0d5c", - "server": "ec97f1c5da1a19609a60874e5af1100c", - "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", - "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", - "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", - "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", - "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", - "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", - "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "uptime-dynamic-settings": "b6289473c8985c79b6c47eebc19a0ca5", - "url": "c7f66a0df8b1b52f17c28c4adb111105", - "visualization": "52d7a13ad68a150c4525b292d23e12cc" - } - }, - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "actionTypeId": { - "type": "keyword" - }, - "config": { - "enabled": false, - "type": "object" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "properties": { - "actionId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "agent_configs": { - "properties": { - "datasources": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "text" - }, - "namespace": { - "type": "keyword" - }, - "revision": { - "type": "integer" - }, - "status": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - }, - "updated_on": { - "type": "keyword" - } - } - }, - "agent_events": { - "properties": { - "action_id": { - "type": "keyword" - }, - "agent_id": { - "type": "keyword" - }, - "config_id": { - "type": "keyword" - }, - "data": { - "type": "text" - }, - "message": { - "type": "text" - }, - "payload": { - "type": "text" - }, - "stream_id": { - "type": "keyword" - }, - "subtype": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "agents": { - "properties": { - "access_api_key_id": { - "type": "keyword" - }, - "actions": { - "properties": { - "created_at": { - "type": "date" - }, - "data": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "sent_at": { - "type": "date" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "active": { - "type": "boolean" - }, - "config_id": { - "type": "keyword" - }, - "config_newest_revision": { - "type": "integer" - }, - "config_revision": { - "type": "integer" - }, - "current_error_events": { - "type": "text" - }, - "default_api_key": { - "type": "keyword" - }, - "enrolled_at": { - "type": "date" - }, - "last_checkin": { - "type": "date" - }, - "last_updated": { - "type": "date" - }, - "local_metadata": { - "type": "text" - }, - "shared_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "user_provided_metadata": { - "type": "text" - }, - "version": { - "type": "keyword" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "actionTypeId": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "consumer": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - }, - "createdBy": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "muteAll": { - "type": "boolean" - }, - "mutedInstanceIds": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "params": { - "enabled": false, - "type": "object" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "scheduledTaskId": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "throttle": { - "type": "keyword" - }, - "updatedAt": { - "type": "date" - }, - "updatedBy": { - "type": "keyword" - }, - "executionStatus": { - "properties": { - "status": { - "type": "keyword" - }, - "lastExecutionDate": { - "type": "date" - }, - "error": { - "properties": { - "reason": { - "type": "keyword" - }, - "message": { - "type": "keyword" - } - } - } - } - } - } - }, - "apm-indices": { - "properties": { - "apm_oss": { - "properties": { - "errorIndices": { - "type": "keyword" - }, - "metricsIndices": { - "type": "keyword" - }, - "onboardingIndices": { - "type": "keyword" - }, - "sourcemapIndices": { - "type": "keyword" - }, - "spanIndices": { - "type": "keyword" - }, - "transactionIndices": { - "type": "keyword" - } - } - } - } - }, - "apm-telemetry": { - "properties": { - "agents": { - "properties": { - "dotnet": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "name": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - } - } - } - } - }, - "go": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - } - } - } - } - }, - "java": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - } - } - } - } - }, - "js-base": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - } - } - } - } - }, - "nodejs": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - } - } - } - } - }, - "python": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - } - } - } - } - }, - "ruby": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - } - } - } - } - }, - "rum-js": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 256, - "type": "keyword" - }, - "name": { - "ignore_above": 256, - "type": "keyword" - }, - "version": { - "ignore_above": 256, - "type": "keyword" - } - } - } - } - } - } - } - } - }, - "cardinality": { - "properties": { - "transaction": { - "properties": { - "name": { - "properties": { - "all_agents": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "rum": { - "properties": { - "1d": { - "type": "long" - } - } - } - } - } - } - }, - "user_agent": { - "properties": { - "original": { - "properties": { - "all_agents": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "rum": { - "properties": { - "1d": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "counts": { - "properties": { - "agent_configuration": { - "properties": { - "all": { - "type": "long" - } - } - }, - "error": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "max_error_groups_per_service": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "max_transaction_groups_per_service": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "metric": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "onboarding": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "services": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "sourcemap": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "span": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "traces": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "transaction": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - } - } - }, - "has_any_services": { - "type": "boolean" - }, - "indices": { - "properties": { - "all": { - "properties": { - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - } - } - } - } - }, - "shards": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "integrations": { - "properties": { - "ml": { - "properties": { - "all_jobs_count": { - "type": "long" - } - } - } - } - }, - "retainment": { - "properties": { - "error": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "metric": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "onboarding": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "span": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "transaction": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "services_per_agent": { - "properties": { - "dotnet": { - "null_value": 0, - "type": "long" - }, - "go": { - "null_value": 0, - "type": "long" - }, - "java": { - "null_value": 0, - "type": "long" - }, - "js-base": { - "null_value": 0, - "type": "long" - }, - "nodejs": { - "null_value": 0, - "type": "long" - }, - "python": { - "null_value": 0, - "type": "long" - }, - "ruby": { - "null_value": 0, - "type": "long" - }, - "rum-js": { - "null_value": 0, - "type": "long" - } - } - }, - "tasks": { - "properties": { - "agent_configuration": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "agents": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "cardinality": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "groupings": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "indices_stats": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "integrations": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "processor_events": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "services": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "versions": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - } - } - }, - "version": { - "properties": { - "apm_server": { - "properties": { - "major": { - "type": "long" - }, - "minor": { - "type": "long" - }, - "patch": { - "type": "long" - } - } - } - } - } - } - }, - "application_usage_totals": { - "properties": { - "appId": { - "type": "keyword" - }, - "minutesOnScreen": { - "type": "float" - }, - "numberOfClicks": { - "type": "long" - } - } - }, - "application_usage_transactional": { - "properties": { - "appId": { - "type": "keyword" - }, - "minutesOnScreen": { - "type": "float" - }, - "numberOfClicks": { - "type": "long" - }, - "timestamp": { - "type": "date" - } - } - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "cases": { - "properties": { - "closed_at": { - "type": "date" - }, - "closed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "description": { - "type": "text" - }, - "external_service": { - "properties": { - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "external_id": { - "type": "keyword" - }, - "external_title": { - "type": "text" - }, - "external_url": { - "type": "text" - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "status": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-comments": { - "properties": { - "comment": { - "type": "text" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-configure": { - "properties": { - "closure_type": { - "type": "keyword" - }, - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-user-actions": { - "properties": { - "action": { - "type": "keyword" - }, - "action_at": { - "type": "date" - }, - "action_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "action_field": { - "type": "keyword" - }, - "new_value": { - "type": "text" - }, - "old_value": { - "type": "text" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "dateFormat:tz": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "datasources": { - "properties": { - "config_id": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "enabled": { - "type": "boolean" - }, - "inputs": { - "properties": { - "config": { - "type": "flattened" - }, - "enabled": { - "type": "boolean" - }, - "processors": { - "type": "keyword" - }, - "streams": { - "properties": { - "config": { - "type": "flattened" - }, - "dataset": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "processors": { - "type": "keyword" - } - }, - "type": "nested" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "output_id": { - "type": "keyword" - }, - "package": { - "properties": { - "name": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "revision": { - "type": "integer" - } - } - }, - "enrollment_api_keys": { - "properties": { - "active": { - "type": "boolean" - }, - "api_key": { - "type": "binary" - }, - "api_key_id": { - "type": "keyword" - }, - "config_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "expire_at": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - } - } - }, - "epm-package": { - "properties": { - "installed": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "infrastructure-ui-source": { - "properties": { - "description": { - "type": "text" - }, - "fields": { - "properties": { - "container": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "pod": { - "type": "keyword" - }, - "tiebreaker": { - "type": "keyword" - }, - "timestamp": { - "type": "keyword" - } - } - }, - "logAlias": { - "type": "keyword" - }, - "logColumns": { - "properties": { - "fieldColumn": { - "properties": { - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - } - } - }, - "messageColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "timestampColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - } - }, - "type": "nested" - }, - "metricAlias": { - "type": "keyword" - }, - "name": { - "type": "text" - } - } - }, - "inventory-view": { - "properties": { - "autoBounds": { - "type": "boolean" - }, - "autoReload": { - "type": "boolean" - }, - "boundsOverride": { - "properties": { - "max": { - "type": "integer" - }, - "min": { - "type": "integer" - } - } - }, - "customMetrics": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "label": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "customOptions": { - "properties": { - "field": { - "type": "keyword" - }, - "text": { - "type": "keyword" - } - }, - "type": "nested" - }, - "filterQuery": { - "properties": { - "expression": { - "type": "keyword" - }, - "kind": { - "type": "keyword" - } - } - }, - "groupBy": { - "properties": { - "field": { - "type": "keyword" - }, - "label": { - "type": "keyword" - } - }, - "type": "nested" - }, - "metric": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "label": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "name": { - "type": "keyword" - }, - "nodeType": { - "type": "keyword" - }, - "time": { - "type": "integer" - }, - "view": { - "type": "keyword" - } - } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "lens": { - "properties": { - "expression": { - "index": false, - "type": "keyword" - }, - "state": { - "type": "flattened" - }, - "title": { - "type": "text" - }, - "visualizationType": { - "type": "keyword" - } - } - }, - "lens-ui-telemetry": { - "properties": { - "count": { - "type": "integer" - }, - "date": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "map": { - "properties": { - "bounds": { - "type": "geo_shape" - }, - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps-telemetry": { - "properties": { - "attributesPerMap": { - "properties": { - "dataSourcesCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - }, - "emsVectorLayersCount": { - "dynamic": "true", - "type": "object" - }, - "layerTypesCount": { - "dynamic": "true", - "type": "object" - }, - "layersCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - } - } - }, - "indexPatternsWithGeoFieldCount": { - "type": "long" - }, - "mapsTotalCount": { - "type": "long" - }, - "settings": { - "properties": { - "showMapVisualizationTypes": { - "type": "boolean" - } - } - }, - "timeCaptured": { - "type": "date" - } - } - }, - "metrics-explorer-view": { - "properties": { - "chartOptions": { - "properties": { - "stack": { - "type": "boolean" - }, - "type": { - "type": "keyword" - }, - "yAxisMode": { - "type": "keyword" - } - } - }, - "currentTimerange": { - "properties": { - "from": { - "type": "keyword" - }, - "interval": { - "type": "keyword" - }, - "to": { - "type": "keyword" - } - } - }, - "name": { - "type": "keyword" - }, - "options": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "filterQuery": { - "type": "keyword" - }, - "groupBy": { - "type": "keyword" - }, - "limit": { - "type": "integer" - }, - "metrics": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "color": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "label": { - "type": "keyword" - } - }, - "type": "nested" - } - } - } - } - }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "space": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "ml-telemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "namespace": { - "type": "keyword" - }, - "outputs": { - "properties": { - "api_key": { - "type": "keyword" - }, - "ca_sha256": { - "type": "keyword" - }, - "config": { - "type": "flattened" - }, - "fleet_enroll_password": { - "type": "binary" - }, - "fleet_enroll_username": { - "type": "binary" - }, - "hosts": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "query": { - "properties": { - "description": { - "type": "text" - }, - "filters": { - "enabled": false, - "type": "object" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "index": false, - "type": "keyword" - } - } - }, - "timefilter": { - "enabled": false, - "type": "object" - }, - "title": { - "type": "text" - } - } - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "siem-detection-engine-rule-status": { - "properties": { - "alertId": { - "type": "keyword" - }, - "bulkCreateTimeDurations": { - "type": "float" - }, - "gap": { - "type": "text" - }, - "lastFailureAt": { - "type": "date" - }, - "lastFailureMessage": { - "type": "text" - }, - "lastLookBackDate": { - "type": "date" - }, - "lastSuccessAt": { - "type": "date" - }, - "lastSuccessMessage": { - "type": "text" - }, - "searchAfterTimeDurations": { - "type": "float" - }, - "status": { - "type": "keyword" - }, - "statusDate": { - "type": "date" - } - } - }, - "siem-ui-timeline": { - "properties": { - "columns": { - "properties": { - "aggregatable": { - "type": "boolean" - }, - "category": { - "type": "keyword" - }, - "columnHeaderType": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "example": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "indexes": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "placeholder": { - "type": "text" - }, - "searchable": { - "type": "boolean" - }, - "type": { - "type": "keyword" - } - } - }, - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "dataProviders": { - "properties": { - "and": { - "properties": { - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - } - } - }, - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - } - } - }, - "dateRange": { - "properties": { - "end": { - "type": "date" - }, - "start": { - "type": "date" - } - } - }, - "description": { - "type": "text" - }, - "eventType": { - "type": "keyword" - }, - "favorite": { - "properties": { - "favoriteDate": { - "type": "date" - }, - "fullName": { - "type": "text" - }, - "keySearch": { - "type": "text" - }, - "userName": { - "type": "text" - } - } - }, - "filters": { - "properties": { - "exists": { - "type": "text" - }, - "match_all": { - "type": "text" - }, - "meta": { - "properties": { - "alias": { - "type": "text" - }, - "controlledBy": { - "type": "text" - }, - "disabled": { - "type": "boolean" - }, - "field": { - "type": "text" - }, - "formattedValue": { - "type": "text" - }, - "index": { - "type": "keyword" - }, - "key": { - "type": "keyword" - }, - "negate": { - "type": "boolean" - }, - "params": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "value": { - "type": "text" - } - } - }, - "missing": { - "type": "text" - }, - "query": { - "type": "text" - }, - "range": { - "type": "text" - }, - "script": { - "type": "text" - } - } - }, - "kqlMode": { - "type": "keyword" - }, - "kqlQuery": { - "properties": { - "filterQuery": { - "properties": { - "kuery": { - "properties": { - "expression": { - "type": "text" - }, - "kind": { - "type": "keyword" - } - } - }, - "serializedQuery": { - "type": "text" - } - } - } - } - }, - "savedQueryId": { - "type": "keyword" - }, - "sort": { - "properties": { - "columnId": { - "type": "keyword" - }, - "sortDirection": { - "type": "keyword" - } - } - }, - "title": { - "type": "text" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-note": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-pinned-event": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "disabledFeatures": { - "type": "keyword" - }, - "imageUrl": { - "index": false, - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "spaceId": { - "type": "keyword" - }, - "telemetry": { - "properties": { - "allowChangingOptInStatus": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "lastReported": { - "type": "date" - }, - "lastVersionChecked": { - "type": "keyword" - }, - "reportFailureCount": { - "type": "integer" - }, - "reportFailureVersion": { - "type": "keyword" - }, - "sendUsageFrom": { - "type": "keyword" - }, - "userHasSeenNotice": { - "type": "boolean" - } - } - }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "tsvb-validation-telemetry": { - "properties": { - "failedRequests": { - "type": "long" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "dynamic": "true", - "properties": { - "indexName": { - "type": "keyword" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "uptime-dynamic-settings": { - "properties": { - "heartbeatIndices": { - "type": "keyword" - } - } - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - ".siem-signals-default": { - "is_write_index": true - } - }, - "index": ".siem-signals-default-000001", - "mappings": { - "dynamic": "false", - "_meta": { - "version": 3 - }, - "properties": { - "@timestamp": { - "type": "date" - }, - "agent": { - "properties": { - "ephemeral_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "client": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "cloud": { - "properties": { - "account": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "availability_zone": { - "type": "keyword", - "ignore_above": 1024 - }, - "instance": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "machine": { - "properties": { - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "provider": { - "type": "keyword", - "ignore_above": 1024 - }, - "region": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "container": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "image": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "tag": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "labels": { - "type": "object" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "runtime": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "destination": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "dll": { - "properties": { - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "dns": { - "properties": { - "answers": { - "properties": { - "class": { - "type": "keyword", - "ignore_above": 1024 - }, - "data": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "ttl": { - "type": "long" - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "header_flags": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "op_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "question": { - "properties": { - "class": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "subdomain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "resolved_ip": { - "type": "ip" - }, - "response_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ecs": { - "properties": { - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "error": { - "properties": { - "code": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "message": { - "type": "text", - "norms": false - }, - "stack_trace": { - "type": "keyword", - "index": false, - "doc_values": false, - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "event": { - "properties": { - "action": { - "type": "keyword", - "ignore_above": 1024 - }, - "category": { - "type": "keyword", - "ignore_above": 1024 - }, - "code": { - "type": "keyword", - "ignore_above": 1024 - }, - "created": { - "type": "date" - }, - "dataset": { - "type": "keyword", - "ignore_above": 1024 - }, - "duration": { - "type": "long" - }, - "end": { - "type": "date" - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "ingested": { - "type": "date" - }, - "kind": { - "type": "keyword", - "ignore_above": 1024 - }, - "module": { - "type": "keyword", - "ignore_above": 1024 - }, - "original": { - "type": "keyword", - "index": false, - "doc_values": false, - "ignore_above": 1024 - }, - "outcome": { - "type": "keyword", - "ignore_above": 1024 - }, - "provider": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "risk_score": { - "type": "float" - }, - "risk_score_norm": { - "type": "float" - }, - "sequence": { - "type": "long" - }, - "severity": { - "type": "long" - }, - "start": { - "type": "date" - }, - "timezone": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "url": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "file": { - "properties": { - "accessed": { - "type": "date" - }, - "attributes": { - "type": "keyword", - "ignore_above": 1024 - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "created": { - "type": "date" - }, - "ctime": { - "type": "date" - }, - "device": { - "type": "keyword", - "ignore_above": 1024 - }, - "directory": { - "type": "keyword", - "ignore_above": 1024 - }, - "drive_letter": { - "type": "keyword", - "ignore_above": 1 - }, - "extension": { - "type": "keyword", - "ignore_above": 1024 - }, - "gid": { - "type": "keyword", - "ignore_above": 1024 - }, - "group": { - "type": "keyword", - "ignore_above": 1024 - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "inode": { - "type": "keyword", - "ignore_above": 1024 - }, - "mime_type": { - "type": "keyword", - "ignore_above": 1024 - }, - "mode": { - "type": "keyword", - "ignore_above": 1024 - }, - "mtime": { - "type": "date" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "owner": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "size": { - "type": "long" - }, - "target_path": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "uid": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword", - "ignore_above": 1024 - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hostname": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "uptime": { - "type": "long" - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "http": { - "properties": { - "request": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "method": { - "type": "keyword", - "ignore_above": 1024 - }, - "referrer": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "response": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "status_code": { - "type": "long" - } - } - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "interface": { - "properties": { - "alias": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "labels": { - "type": "object" - }, - "log": { - "properties": { - "level": { - "type": "keyword", - "ignore_above": 1024 - }, - "logger": { - "type": "keyword", - "ignore_above": 1024 - }, - "origin": { - "properties": { - "file": { - "properties": { - "line": { - "type": "integer" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "function": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "original": { - "type": "keyword", - "index": false, - "doc_values": false, - "ignore_above": 1024 - }, - "syslog": { - "properties": { - "facility": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "priority": { - "type": "long" - }, - "severity": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - } - } - }, - "message": { - "type": "text", - "norms": false - }, - "network": { - "properties": { - "application": { - "type": "keyword", - "ignore_above": 1024 - }, - "bytes": { - "type": "long" - }, - "community_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "direction": { - "type": "keyword", - "ignore_above": 1024 - }, - "forwarded_ip": { - "type": "ip" - }, - "iana_number": { - "type": "keyword", - "ignore_above": 1024 - }, - "inner": { - "properties": { - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "packets": { - "type": "long" - }, - "protocol": { - "type": "keyword", - "ignore_above": 1024 - }, - "transport": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "observer": { - "properties": { - "egress": { - "properties": { - "interface": { - "properties": { - "alias": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "zone": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hostname": { - "type": "keyword", - "ignore_above": 1024 - }, - "ingress": { - "properties": { - "interface": { - "properties": { - "alias": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "zone": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - }, - "serial_number": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "vendor": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "organization": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "package": { - "properties": { - "architecture": { - "type": "keyword", - "ignore_above": 1024 - }, - "build_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "checksum": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "install_scope": { - "type": "keyword", - "ignore_above": 1024 - }, - "installed": { - "type": "date" - }, - "license": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "size": { - "type": "long" - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "process": { - "properties": { - "args": { - "type": "keyword", - "ignore_above": 1024 - }, - "args_count": { - "type": "long" - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "command_line": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "entity_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "executable": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "parent": { - "properties": { - "args": { - "type": "keyword", - "ignore_above": 1024 - }, - "args_count": { - "type": "long" - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "command_line": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "entity_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "executable": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "title": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "title": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "registry": { - "properties": { - "data": { - "properties": { - "bytes": { - "type": "keyword", - "ignore_above": 1024 - }, - "strings": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hive": { - "type": "keyword", - "ignore_above": 1024 - }, - "key": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "value": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "related": { - "properties": { - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "ip": { - "type": "ip" - }, - "user": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "rule": { - "properties": { - "author": { - "type": "keyword", - "ignore_above": 1024 - }, - "category": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "license": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "ruleset": { - "type": "keyword", - "ignore_above": 1024 - }, - "uuid": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "server": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "service": { - "properties": { - "ephemeral_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "node": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "state": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "signal": { - "properties": { - "ancestors": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "depth": { - "type": "integer" - }, - "group": { - "properties": { - "id": { - "type": "keyword" - }, - "index": { - "type": "integer" - } - } - }, - "original_event": { - "properties": { - "action": { - "type": "keyword" - }, - "category": { - "type": "keyword" - }, - "code": { - "type": "keyword" - }, - "created": { - "type": "date" - }, - "dataset": { - "type": "keyword" - }, - "duration": { - "type": "long" - }, - "end": { - "type": "date" - }, - "hash": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "kind": { - "type": "keyword" - }, - "module": { - "type": "keyword" - }, - "original": { - "type": "keyword", - "index": false, - "doc_values": false - }, - "outcome": { - "type": "keyword" - }, - "provider": { - "type": "keyword" - }, - "risk_score": { - "type": "float" - }, - "risk_score_norm": { - "type": "float" - }, - "sequence": { - "type": "long" - }, - "severity": { - "type": "long" - }, - "start": { - "type": "date" - }, - "timezone": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "original_signal": { - "type": "object", - "dynamic": "false", - "enabled": false - }, - "original_time": { - "type": "date" - }, - "parent": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "parents": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "rule": { - "properties": { - "author": { - "type": "keyword" - }, - "building_block_type": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "enabled": { - "type": "keyword" - }, - "false_positives": { - "type": "keyword" - }, - "filters": { - "type": "object" - }, - "from": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "immutable": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "interval": { - "type": "keyword" - }, - "language": { - "type": "keyword" - }, - "license": { - "type": "keyword" - }, - "max_signals": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "output_index": { - "type": "keyword" - }, - "query": { - "type": "keyword" - }, - "references": { - "type": "keyword" - }, - "risk_score": { - "type": "float" - }, - "risk_score_mapping": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "value": { - "type": "keyword" - } - } - }, - "rule_id": { - "type": "keyword" - }, - "rule_name_override": { - "type": "keyword" - }, - "saved_id": { - "type": "keyword" - }, - "severity": { - "type": "keyword" - }, - "severity_mapping": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "severity": { - "type": "keyword" - }, - "value": { - "type": "keyword" - } - } - }, - "size": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "threat": { - "properties": { - "framework": { - "type": "keyword" - }, - "tactic": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "reference": { - "type": "keyword" - } - } - }, - "technique": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "reference": { - "type": "keyword" - } - } - } - } - }, - "threshold": { - "properties": { - "field": { - "type": "keyword" - }, - "value": { - "type": "float" - } - } - }, - "timeline_id": { - "type": "keyword" - }, - "timeline_title": { - "type": "keyword" - }, - "timestamp_override": { - "type": "keyword" - }, - "to": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "status": { - "type": "keyword" - }, - "threshold_count": { - "type": "float" - }, - "threshold_result": { - "properties": { - "count": { - "type": "long" - }, - "value": { - "type": "keyword" - } - } - } - } - }, - "source": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "tags": { - "type": "keyword", - "ignore_above": 1024 - }, - "threat": { - "properties": { - "framework": { - "type": "keyword", - "ignore_above": 1024 - }, - "tactic": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "technique": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "tls": { - "properties": { - "cipher": { - "type": "keyword", - "ignore_above": 1024 - }, - "client": { - "properties": { - "certificate": { - "type": "keyword", - "ignore_above": 1024 - }, - "certificate_chain": { - "type": "keyword", - "ignore_above": 1024 - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "issuer": { - "type": "keyword", - "ignore_above": 1024 - }, - "ja3": { - "type": "keyword", - "ignore_above": 1024 - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "server_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject": { - "type": "keyword", - "ignore_above": 1024 - }, - "supported_ciphers": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "curve": { - "type": "keyword", - "ignore_above": 1024 - }, - "established": { - "type": "boolean" - }, - "next_protocol": { - "type": "keyword", - "ignore_above": 1024 - }, - "resumed": { - "type": "boolean" - }, - "server": { - "properties": { - "certificate": { - "type": "keyword", - "ignore_above": 1024 - }, - "certificate_chain": { - "type": "keyword", - "ignore_above": 1024 - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "issuer": { - "type": "keyword", - "ignore_above": 1024 - }, - "ja3s": { - "type": "keyword", - "ignore_above": 1024 - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "subject": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - }, - "version_protocol": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "trace": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "transaction": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "url": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "extension": { - "type": "keyword", - "ignore_above": 1024 - }, - "fragment": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "original": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "password": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "port": { - "type": "long" - }, - "query": { - "type": "keyword", - "ignore_above": 1024 - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "scheme": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "username": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "user_agent": { - "properties": { - "device": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "original": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vulnerability": { - "properties": { - "category": { - "type": "keyword", - "ignore_above": 1024 - }, - "classification": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "enumeration": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "report_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "scanner": { - "properties": { - "vendor": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "score": { - "properties": { - "base": { - "type": "float" - }, - "environmental": { - "type": "float" - }, - "temporal": { - "type": "float" - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "severity": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "settings": { - "index": { - "lifecycle": { - "name": ".siem-signals-default", - "rollover_alias": ".siem-signals-default" - }, - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} diff --git a/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/data.json.gz deleted file mode 100644 index 0bec9975031467ec2e01b4dce2cb464ba59748d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42571 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 D_fun! diff --git a/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/mappings.json b/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/mappings.json deleted file mode 100644 index 7ef00495390ee..0000000000000 --- a/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/mappings.json +++ /dev/null @@ -1,2967 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "action": "6e96ac5e648f57523879661ea72525b7", - "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", - "agent_actions": "ed270b46812f0fa1439366c428a2cf17", - "agent_configs": "38abaf89513877745c359e7700c0c66a", - "agent_events": "3231653fafe4ef3196fe3b32ab774bf2", - "agents": "c3eeb7b9d97176f15f6d126370ab23c7", - "alert": "7b44fba6773e37c806ce290ea9b7024e", - "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", - "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", - "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", - "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", - "canvas-element": "7390014e1091044523666d97247392fc", - "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "cases": "08b8b110dbca273d37e8aef131ecab61", - "cases-comments": "c2061fb929f585df57425102fa928b4b", - "cases-configure": "42711cbb311976c0687853f4c1354572", - "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "config": "ae24d22d5986d04124cc6568f771066f", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "datasources": "d4bc0c252b2b5683ff21ea32d00acffc", - "enrollment_api_keys": "28b91e20b105b6f928e2012600085d8f", - "epm-package": "0be91c6758421dd5d0f1a58e9e5bc7c3", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", - "inventory-view": "9ecce5b58867403613d82fe496470b34", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "lens": "21c3ea0763beb1ecb0162529706b88c5", - "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", - "map": "23d7aa4a720d4938ccde3983f87bd58d", - "maps-telemetry": "268da3a48066123fc5baf35abaa55014", - "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "namespace": "2f4316de49999235636386fe51dc06c1", - "outputs": "aee9782e0d500b867859650a36280165", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "181661168bbadd1eff5902361e2a0d5c", - "server": "ec97f1c5da1a19609a60874e5af1100c", - "siem-detection-engine-rule-actions": "90eee2e4635260f4be0a1da8f5bc0aa0", - "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", - "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", - "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", - "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", - "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", - "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", - "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "uptime-dynamic-settings": "b6289473c8985c79b6c47eebc19a0ca5", - "url": "c7f66a0df8b1b52f17c28c4adb111105", - "visualization": "52d7a13ad68a150c4525b292d23e12cc" - } - }, - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "actionTypeId": { - "type": "keyword" - }, - "config": { - "enabled": false, - "type": "object" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "properties": { - "actionId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "agent_actions": { - "properties": { - "agent_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "data": { - "type": "flattened" - }, - "sent_at": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "agent_configs": { - "properties": { - "datasources": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "text" - }, - "namespace": { - "type": "keyword" - }, - "revision": { - "type": "integer" - }, - "status": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - }, - "updated_on": { - "type": "keyword" - } - } - }, - "agent_events": { - "properties": { - "action_id": { - "type": "keyword" - }, - "agent_id": { - "type": "keyword" - }, - "config_id": { - "type": "keyword" - }, - "data": { - "type": "text" - }, - "message": { - "type": "text" - }, - "payload": { - "type": "text" - }, - "stream_id": { - "type": "keyword" - }, - "subtype": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "agents": { - "properties": { - "access_api_key_id": { - "type": "keyword" - }, - "active": { - "type": "boolean" - }, - "config_id": { - "type": "keyword" - }, - "config_newest_revision": { - "type": "integer" - }, - "config_revision": { - "type": "integer" - }, - "current_error_events": { - "type": "text" - }, - "default_api_key": { - "type": "keyword" - }, - "enrolled_at": { - "type": "date" - }, - "last_checkin": { - "type": "date" - }, - "last_updated": { - "type": "date" - }, - "local_metadata": { - "type": "text" - }, - "shared_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "user_provided_metadata": { - "type": "text" - }, - "version": { - "type": "keyword" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "actionTypeId": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "consumer": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - }, - "createdBy": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "muteAll": { - "type": "boolean" - }, - "mutedInstanceIds": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "params": { - "enabled": false, - "type": "object" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "scheduledTaskId": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "throttle": { - "type": "keyword" - }, - "updatedAt": { - "type": "date" - }, - "updatedBy": { - "type": "keyword" - } - } - }, - "apm-indices": { - "properties": { - "apm_oss": { - "properties": { - "errorIndices": { - "type": "keyword" - }, - "metricsIndices": { - "type": "keyword" - }, - "onboardingIndices": { - "type": "keyword" - }, - "sourcemapIndices": { - "type": "keyword" - }, - "spanIndices": { - "type": "keyword" - }, - "transactionIndices": { - "type": "keyword" - } - } - } - } - }, - "apm-telemetry": { - "properties": { - "agents": { - "properties": { - "dotnet": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "go": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "java": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "js-base": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "nodejs": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "python": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "ruby": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "rum-js": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - } - } - }, - "cardinality": { - "properties": { - "transaction": { - "properties": { - "name": { - "properties": { - "all_agents": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "rum": { - "properties": { - "1d": { - "type": "long" - } - } - } - } - } - } - }, - "user_agent": { - "properties": { - "original": { - "properties": { - "all_agents": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "rum": { - "properties": { - "1d": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "counts": { - "properties": { - "agent_configuration": { - "properties": { - "all": { - "type": "long" - } - } - }, - "error": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "max_error_groups_per_service": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "max_transaction_groups_per_service": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "metric": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "onboarding": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "services": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "sourcemap": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "span": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "traces": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "transaction": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - } - } - }, - "has_any_services": { - "type": "boolean" - }, - "indices": { - "properties": { - "all": { - "properties": { - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - } - } - } - } - }, - "shards": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "integrations": { - "properties": { - "ml": { - "properties": { - "all_jobs_count": { - "type": "long" - } - } - } - } - }, - "retainment": { - "properties": { - "error": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "metric": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "onboarding": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "span": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "transaction": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "services_per_agent": { - "properties": { - "dotnet": { - "null_value": 0, - "type": "long" - }, - "go": { - "null_value": 0, - "type": "long" - }, - "java": { - "null_value": 0, - "type": "long" - }, - "js-base": { - "null_value": 0, - "type": "long" - }, - "nodejs": { - "null_value": 0, - "type": "long" - }, - "python": { - "null_value": 0, - "type": "long" - }, - "ruby": { - "null_value": 0, - "type": "long" - }, - "rum-js": { - "null_value": 0, - "type": "long" - } - } - }, - "tasks": { - "properties": { - "agent_configuration": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "agents": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "cardinality": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "groupings": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "indices_stats": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "integrations": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "processor_events": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "services": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "versions": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - } - } - }, - "version": { - "properties": { - "apm_server": { - "properties": { - "major": { - "type": "long" - }, - "minor": { - "type": "long" - }, - "patch": { - "type": "long" - } - } - } - } - } - } - }, - "application_usage_totals": { - "properties": { - "appId": { - "type": "keyword" - }, - "minutesOnScreen": { - "type": "float" - }, - "numberOfClicks": { - "type": "long" - } - } - }, - "application_usage_transactional": { - "properties": { - "appId": { - "type": "keyword" - }, - "minutesOnScreen": { - "type": "float" - }, - "numberOfClicks": { - "type": "long" - }, - "timestamp": { - "type": "date" - } - } - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "cases": { - "properties": { - "closed_at": { - "type": "date" - }, - "closed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "description": { - "type": "text" - }, - "external_service": { - "properties": { - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "external_id": { - "type": "keyword" - }, - "external_title": { - "type": "text" - }, - "external_url": { - "type": "text" - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "status": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-comments": { - "properties": { - "comment": { - "type": "text" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-configure": { - "properties": { - "closure_type": { - "type": "keyword" - }, - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-user-actions": { - "properties": { - "action": { - "type": "keyword" - }, - "action_at": { - "type": "date" - }, - "action_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "action_field": { - "type": "keyword" - }, - "new_value": { - "type": "text" - }, - "old_value": { - "type": "text" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "datasources": { - "properties": { - "config_id": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "enabled": { - "type": "boolean" - }, - "inputs": { - "properties": { - "config": { - "type": "flattened" - }, - "enabled": { - "type": "boolean" - }, - "processors": { - "type": "keyword" - }, - "streams": { - "properties": { - "config": { - "type": "flattened" - }, - "dataset": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "processors": { - "type": "keyword" - } - }, - "type": "nested" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "output_id": { - "type": "keyword" - }, - "package": { - "properties": { - "name": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "revision": { - "type": "integer" - } - } - }, - "enrollment_api_keys": { - "properties": { - "active": { - "type": "boolean" - }, - "api_key": { - "type": "binary" - }, - "api_key_id": { - "type": "keyword" - }, - "config_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "expire_at": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - } - } - }, - "epm-package": { - "properties": { - "installed": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "internal": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "infrastructure-ui-source": { - "properties": { - "description": { - "type": "text" - }, - "fields": { - "properties": { - "container": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "pod": { - "type": "keyword" - }, - "tiebreaker": { - "type": "keyword" - }, - "timestamp": { - "type": "keyword" - } - } - }, - "logAlias": { - "type": "keyword" - }, - "logColumns": { - "properties": { - "fieldColumn": { - "properties": { - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - } - } - }, - "messageColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "timestampColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - } - }, - "type": "nested" - }, - "metricAlias": { - "type": "keyword" - }, - "name": { - "type": "text" - } - } - }, - "inventory-view": { - "properties": { - "autoBounds": { - "type": "boolean" - }, - "autoReload": { - "type": "boolean" - }, - "boundsOverride": { - "properties": { - "max": { - "type": "integer" - }, - "min": { - "type": "integer" - } - } - }, - "customMetrics": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "label": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "customOptions": { - "properties": { - "field": { - "type": "keyword" - }, - "text": { - "type": "keyword" - } - }, - "type": "nested" - }, - "filterQuery": { - "properties": { - "expression": { - "type": "keyword" - }, - "kind": { - "type": "keyword" - } - } - }, - "groupBy": { - "properties": { - "field": { - "type": "keyword" - }, - "label": { - "type": "keyword" - } - }, - "type": "nested" - }, - "metric": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "label": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "name": { - "type": "keyword" - }, - "nodeType": { - "type": "keyword" - }, - "time": { - "type": "integer" - }, - "view": { - "type": "keyword" - } - } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "lens": { - "properties": { - "expression": { - "index": false, - "type": "keyword" - }, - "state": { - "type": "flattened" - }, - "title": { - "type": "text" - }, - "visualizationType": { - "type": "keyword" - } - } - }, - "lens-ui-telemetry": { - "properties": { - "count": { - "type": "integer" - }, - "date": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "map": { - "properties": { - "bounds": { - "type": "geo_shape" - }, - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps-telemetry": { - "properties": { - "attributesPerMap": { - "properties": { - "dataSourcesCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - }, - "emsVectorLayersCount": { - "dynamic": "true", - "type": "object" - }, - "layerTypesCount": { - "dynamic": "true", - "type": "object" - }, - "layersCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - } - } - }, - "indexPatternsWithGeoFieldCount": { - "type": "long" - }, - "mapsTotalCount": { - "type": "long" - }, - "settings": { - "properties": { - "showMapVisualizationTypes": { - "type": "boolean" - } - } - }, - "timeCaptured": { - "type": "date" - } - } - }, - "metrics-explorer-view": { - "properties": { - "chartOptions": { - "properties": { - "stack": { - "type": "boolean" - }, - "type": { - "type": "keyword" - }, - "yAxisMode": { - "type": "keyword" - } - } - }, - "currentTimerange": { - "properties": { - "from": { - "type": "keyword" - }, - "interval": { - "type": "keyword" - }, - "to": { - "type": "keyword" - } - } - }, - "name": { - "type": "keyword" - }, - "options": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "filterQuery": { - "type": "keyword" - }, - "groupBy": { - "type": "keyword" - }, - "limit": { - "type": "integer" - }, - "metrics": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "color": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "label": { - "type": "keyword" - } - }, - "type": "nested" - } - } - } - } - }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "space": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "ml-telemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "namespace": { - "type": "keyword" - }, - "outputs": { - "properties": { - "api_key": { - "type": "keyword" - }, - "ca_sha256": { - "type": "keyword" - }, - "config": { - "type": "flattened" - }, - "fleet_enroll_password": { - "type": "binary" - }, - "fleet_enroll_username": { - "type": "binary" - }, - "hosts": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "query": { - "properties": { - "description": { - "type": "text" - }, - "filters": { - "enabled": false, - "type": "object" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "index": false, - "type": "keyword" - } - } - }, - "timefilter": { - "enabled": false, - "type": "object" - }, - "title": { - "type": "text" - } - } - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "siem-detection-engine-rule-actions": { - "properties": { - "actions": { - "properties": { - "action_type_id": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "params": { - "dynamic": "true", - "type": "object" - } - } - }, - "alertThrottle": { - "type": "keyword" - }, - "ruleAlertId": { - "type": "keyword" - }, - "ruleThrottle": { - "type": "keyword" - } - } - }, - "siem-detection-engine-rule-status": { - "properties": { - "alertId": { - "type": "keyword" - }, - "bulkCreateTimeDurations": { - "type": "float" - }, - "gap": { - "type": "text" - }, - "lastFailureAt": { - "type": "date" - }, - "lastFailureMessage": { - "type": "text" - }, - "lastLookBackDate": { - "type": "date" - }, - "lastSuccessAt": { - "type": "date" - }, - "lastSuccessMessage": { - "type": "text" - }, - "searchAfterTimeDurations": { - "type": "float" - }, - "status": { - "type": "keyword" - }, - "statusDate": { - "type": "date" - } - } - }, - "siem-ui-timeline": { - "properties": { - "columns": { - "properties": { - "aggregatable": { - "type": "boolean" - }, - "category": { - "type": "keyword" - }, - "columnHeaderType": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "example": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "indexes": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "placeholder": { - "type": "text" - }, - "searchable": { - "type": "boolean" - }, - "type": { - "type": "keyword" - } - } - }, - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "dataProviders": { - "properties": { - "and": { - "properties": { - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - } - } - }, - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - } - } - }, - "dateRange": { - "properties": { - "end": { - "type": "date" - }, - "start": { - "type": "date" - } - } - }, - "description": { - "type": "text" - }, - "eventType": { - "type": "keyword" - }, - "favorite": { - "properties": { - "favoriteDate": { - "type": "date" - }, - "fullName": { - "type": "text" - }, - "keySearch": { - "type": "text" - }, - "userName": { - "type": "text" - } - } - }, - "filters": { - "properties": { - "exists": { - "type": "text" - }, - "match_all": { - "type": "text" - }, - "meta": { - "properties": { - "alias": { - "type": "text" - }, - "controlledBy": { - "type": "text" - }, - "disabled": { - "type": "boolean" - }, - "field": { - "type": "text" - }, - "formattedValue": { - "type": "text" - }, - "index": { - "type": "keyword" - }, - "key": { - "type": "keyword" - }, - "negate": { - "type": "boolean" - }, - "params": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "value": { - "type": "text" - } - } - }, - "missing": { - "type": "text" - }, - "query": { - "type": "text" - }, - "range": { - "type": "text" - }, - "script": { - "type": "text" - } - } - }, - "kqlMode": { - "type": "keyword" - }, - "kqlQuery": { - "properties": { - "filterQuery": { - "properties": { - "kuery": { - "properties": { - "expression": { - "type": "text" - }, - "kind": { - "type": "keyword" - } - } - }, - "serializedQuery": { - "type": "text" - } - } - } - } - }, - "savedQueryId": { - "type": "keyword" - }, - "sort": { - "properties": { - "columnId": { - "type": "keyword" - }, - "sortDirection": { - "type": "keyword" - } - } - }, - "title": { - "type": "text" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-note": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-pinned-event": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "disabledFeatures": { - "type": "keyword" - }, - "imageUrl": { - "index": false, - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "telemetry": { - "properties": { - "allowChangingOptInStatus": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "lastReported": { - "type": "date" - }, - "lastVersionChecked": { - "type": "keyword" - }, - "reportFailureCount": { - "type": "integer" - }, - "reportFailureVersion": { - "type": "keyword" - }, - "sendUsageFrom": { - "type": "keyword" - }, - "userHasSeenNotice": { - "type": "boolean" - } - } - }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "tsvb-validation-telemetry": { - "properties": { - "failedRequests": { - "type": "long" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "dynamic": "true", - "properties": { - "indexName": { - "type": "keyword" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "uptime-dynamic-settings": { - "properties": { - "heartbeatIndices": { - "type": "keyword" - } - } - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/x-pack/test/security_solution_cypress/es_archives/timeline_alerts/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/timeline_alerts/data.json.gz deleted file mode 100644 index 485d9868efd21af89ca7145386c7b95142f17205..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 225608 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�v;L$hnqumq#%Q&~bCD~dtqmHn?q_jJa+^^T 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%0AdcUA0@M3 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

    b7 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

    6HtE@>Q|5mvQH37=0N zY<>28ec$7Qfx>3Z^9%8*C}O})N*SIB$)$}7u@$l1Eeus8#5k)yuU|`Mc2JLPXjgzL{ZXVjVk_=I#a}wYl)^jDfSEG;YX&WD*gsLV%c+*N@+FPl zQS+w?y-rGN*)tYHO`b9W-A61gT7_ftWP;&$cCr_eo|a!?(v$ur6alt9pUiHaec#g5 zm*=pmGgq^8X+vnY1U64+F^s708C4<49bxg%W_4i)QMIDTvv38D2F z_}KdOcv|t^Lhr=&fHI<_gtF}Ja(&aY_P^0)71{cs!TyNTB_-j|SZ#4~lJRWjl!P%1 zJD%^SaxvFXovGbX0xU0~e_7GRMS{VWl5v-8083yXxbFkbFuErb-3DN{AG?tNXJcSz zDKmm_XDL`<4{iQW;k+;RyU87G>!1@e9z(rQm^LM4HesUU1y4v&oLgX{ z%iuUx(=dE-j#Fks4kW$7=x$Zgvr?{46?akLtsO< zwuC!lpTg{)?rUknWN?Tmh^Q`i!s4B1x1kz_9U>t5Y1`ob#Qy$=d?sNQ*#Gv7L2iY)+B=}d{^7=p?Nsb>Zqj9%lk&U*$xk1bNqMfjlaa&7iMEr2^=U`lm&2xS zk$y3LkX+ar#)OjE=3{8~0g7i*{Ry@;(XM9+nRSUWUO))&k+g>Fwn7;n^q0^w))~kg z$Q()(8B#y&PvI*J?Xh{rK&8QJdhTAWW9#F6>;3{=+hJrG5u~Crstl{@17-lahAO`5 zGDO0j2wXoJcmg2db48VYY+ANWS#iiF;e7tZ$W?DVu<%2^K7?2W4Zz2HIvD z?A%1sF#0oRm|Xi|BvXb`^rJWGKod^&U9*KyKoY_bDuBLcu^pl6G80==AER}j4#fv; z70wF#&aK9wMbnJ=rj72#g%_W4_o>a~>6BiOf&ki2po!crFmVbGstQ7WDTe!Z-Jy0sRh1eSqqlRke3gO|VGEBHS4| zOncVQX&0&2XARKM)XhF{9QH?OM{e(Abu`z-T2zX?x_8>&rhoi4+L(74{NiM(9S2s~ z1y?C3s9+s-io15|bK263ybUURPC?!9g}qYr0jJxs`yPwKQP(Ewv@BaXug0eFyhHiz zEmj{!+Z#jMTiPjwxU$berTbo6JNU@)^Xhs+TTlE&o-JMwTHju6-**{7F{eLmL``0L zMBdwafW=M1#b*RNB9_mq`Lu(#{;z6y|E-q7TeaBlsQGL5D0p*~0Og|S-f`YToMzFr z4XdMV@q%f<@qtyl7~hu2mLFvislO5e1klJkoo$u}Tl~cHX7K6V&Y8*Mj>~O``b*!6 zjn66Q5$*z;8OahUw%iyiMbADP2zt-W0+FYXpnoE=^|TnTO2-Sr?!KS3Z+G0DV)Ul# za%*w20Q+q52!dVam;Y(;(tleF@=m?)nf1{GOuw`=d8Np_lI6_~nE3OY@gpSK%$FEz zKJH4{Dm!-C&6&;R4eDUSl&ebQBSY()>6kZQ6FH| z;MP(>9D8SLP>h58HyJZG4mn7@` zX=?9YN0#w?WlxE->|I{<^?v-}y!@dJ<9v}-;9e{9d;Mj@%A`eC)^ca;{D+q6YvlXT z`H;Yc0_glnvrUj0oId}-SN6VG{&1RlJ}vfIHxxOOz2QQAxw&%mGxfKiE6@!T($HJ+ zy7{c)_mxeHnasz<(%*V-Ue+%sASY;;@@xJ=QdUiP^naL@pc$gvb#gC=Qx^6obJ7c^%68H z^H|elQZHKI%JVuckRZ2BnB>nNXRA{2SB$y=PfhV0gp~v@(`mY@L9tJ52i}Q?Vi~Fi z^wk274NIlQ$g(i2pPB;CX(K^KYW+c$s98se8KQBAPR1{GULJB~+p0549|Tz+85Dgg zoi|4yqdR>qq1!aM6O-mk=pmbxgXAt}ho;uz^!fA4_LC!!AjRo}B0ao>*ub!qYat%i zh|txNTs{Gtp_bLK25S)ROP+_=SLP71>?p}LJ`R8c?a~SU7CVkSnLO?m<`z4s+jD!C zOTtLW+@+J62LVd}W-$keuV)XQw_1lh=5wo{e^(+UiTyy!?aTmcMXP>gX;SPB)a=`#B08(B)-IVR7x` zJ2GSY4mZLKWK;sWOuvX*zfoWSd%uNPMI=#DY`t_0m^f`VwaMf9P^hhcb`*w`w;`R> zDXZa@4j^NA01nn3G6RtD>Cd3?Q(B+p?|8Hl!wAp3N-Hb$pZ89QFzkCOlkk*NLMgH* zMydmdc=Q#FHkO@|tY*$PaM-<}2e|BP!T%9u@YrMjBf^3{21{>H*u&C;Xi$;3%NxQ( z;UK^@e&!Z}G?kJdRpWb7yGK|JR!xRNKxzp6DS+OA#T~1)IhyZ~baPfoSmNe-?x369^yf?{i#EoC@BULxGDV zp28PfD_T3Lz5UV`G5IS-a@E_D!`wol{r&dV2jeQ^7w#d%Q}*1HN4}$)05^c!UT&~| zHH;97P;|XV>Azh%I6(RHjd%mo`D$TXk(HoK8vY#0izovtc1cR^+|b;Edir_ho9Iw{ z;3Us>c}6ZMI<=Ky4WY9rAW?D8^kQWEGn+r${=;9bsN|9w6`hmiC+}4}3rjAmlLT$C zQGkQc7r&UquyLc3UVl6hEH~mG(+b+jSdiX;AN!*JDZqYwG8=xjMMl}6OiQ=##Kq|< zXwY-JVL=V=U7dbZMe8|Tek*=mA(@URWObDp636n!1Raimxx?Zp$Ghht*AbCA5I|tr zd>{iqc7p}{kqmLqm{s$mplai%24-62h&pGcHmXX+W6}z8YysEcs01o508$na5_JI# zRes?vZ_!T#t2$08#+tJXg)=4q8y6NmV*?UatrCoOhg-f~5!T7$cs z?1e~+v;Exk^vN85Q-vB*b@by7Op;>`)ZDJU4x9w0HiM%7{+vz zOOH8|w{txGmOi{Nj-Q?ZUXoPD^P;UQa|YEUFKlPino#7Xy>aylL~Z zxv4&_7ndpRa_n}pwY7%aj@-MlZ1fgpPxxm8gxMh+pLENv){m{GDCEV)D#t@%oZ$4X z6ETDv`C5e#gepP9CDO}MqnaNxx3tvS;@ep9s;i`g;WzRhQqa}$4PY8*OK92E3@Vk_ z+>UJg`YA&iPO5(0$of?Jg`g&Al%~=KmJR;Q`z@JJzZ5x^1&Q*$(t?(|Yn6pT!0Wa1 zJj~+t3+ylY`tD^(%lsP7s?$$vc5LyV-p_$wqzr<1&{YC=)ai?PZV1H#+(f9@G&RD^ z`U&sZ*x%R#C-D1Xx>z}(=*IN|zQ%P82E*?)ob5rOu?y)6>4J2YSTzN>Ic1LRpl@MIAzr$y8S#36og9QL1?QNzs=nDU*rR1NI60i5xVkc9s;441-JEZTlS!6wQzQ zlQ0S|5kRDNL;kj*UD!32>s6uU%(2Oosd&*xhO-qp>o5EKeqnMSL*gNb{^-opew_yK zHH0){Xf0STb_ZF7$iNgTPsAv$pkRA@qhA0SH!hkqb^Sq_Vr~8K8U`5fD>aV%4Gg!Y z9~Z2K*$2%B4fL@M`g23Sm$fdKY3rsg7{6Y1TB{A+jOsJ!IZhtO+LIE=>ggrt{}5Iv zIndbs>VpX*i6cocv?x`(Iu;z6PV-x%y;%u+FLX)z3k!!fJ%v$NWFB8!M9PM4z_@D| zcU{!styrwmoiO>@!QnR4yO=*bD()5d6*&qMX)V;J0D1}SF1Q!`v;!jR7=>38P!64S zce~Q2z|M|&9*xb`myG<|e2b17MUQA98FLrwL~JE7IYK*vGtZNf-v306YT^Q>@mRfW zwTNpUpb?WTH1T8wvTBW!c&SH{y0x*DhK#J^ZRh{rkdej5+S02`-2#8r!mOxpFjEz> zV6YVQzwMm9&$VZJo?^3=9D-wV{?s_<@;(6zI|h0C+BH!|W%5Q*(=tOr*GGBHLz}sB z_|MWd0hZRe|7m31QRB|AVA!OYV5 z@~}zk6qtMo!yv96Ui=g;E`KNsfG`P{kXi)uO@0@>8w`Y8wOWR)>ZSVz1?868M9=rv zDl_2TkbH2-DZRzwW@@eXLD*u>$sh?L|Ia|U#{(hf(>HTb+r{d+3{O^%& z6b>0qC&r46q!R4BB*J?15xTpf^C&Nx*Viwd+a6)EAHyZ#87v~8YJ{0X17tUBby7Ub0XfTL3zjf8n+1Qr zXj+v_k66Ay1}Qz~M~!)YWjl@`AyN{$ZfbS(e)i;VOf0LyY1<>0yk`0(2KDUYz8mW> zuCyKbOJ+x1ES8$KNS-^FJcz+@I-|oHL@?w=0QZtxa5bht5!bC4jQZW ztNwOf3S`5D5jLEFsgj;k7sUSiI3<{{YdkCVRwc}SW?8#I7D)mZ?Ip;)p73bl;y`d3 zS!R+?oK+6tO-!7te}>Hde0q_=(i!T`8kH}RkIiv0NIheUSmkp8EUX+E;VPc8p7g4X zEGr{i@2Xb9DhPyC`fm6oM42`!L41Yx7diGBa0w5+Wr@t>;5<-CAJx;r<)R1|jK~j{ zt{xxo&meRf%;W`(2tWjSPLbdono|^#M$d}WUvzv|+dpld7l4TWz`AxUdUY40MQ?}d zA0m8>18=6d@2(T+662a23+Jmc%OlD<4`eb`=jUW_s7LVX{rZ+wN};fTuoKOOKMWWO zsN*amKMW#JziW{C{3lNX5Jn|pZ;n?=&59;PIm|x*R>9eQKfT4q;ai*5e|gZ*Z!Ocj zh&J6>Hs{|vayu^nFMl#9YXiskLqp>WEP<=i-_~j(cFXk>bBQI+RWe^ObXWh^Nxa9> zx$!L*X44|!cfPA&{zv=rSDojwZX8f95ZoLrWaN_BO6Wb>255@Z>8H^jr-j6oP+u@N z!1QD&*C+NSaY&%)ze5xUT()08e^sg7#tw3>oHN&cd4V_4hW!qw3fdXSUZnZmt8c4? z7L>I_yF_`uLXpMM{C|^%Gz&0hd{^XBo0q@m>)4v@V-@~7$NSEt(-Vf{voYDX;XM7^ z(fQnM<9%{EL3rd@_V`6mYA`GXKM%KY+qoNjvgJ{i^t9PlnMIU{Vtg;&&R6(u1lP1Y z0V{gOZqmf4XcXVi*eB9{xf#BPwR^C85HxkyTz^s-e|T8dr>bR#2YStyq_Q-4Dmx1F zxpEj4*F>P1T76BjQwL)V^*XP2D&Vnvw>TSX;E3&``lK%gVErHZA`w{%nj~lksBB{s z)sGz93ExD}MjmDm8N)e+R+bV>1)y@n3?)l>LMP#A?#v^&koMKh5wB>=g3ss-LNS;UkholIy<=OsY=0~q&gUo#ApnE-u^Q!-_ z9YOrX^G~v)H%UVm38?O*xAR<9_{p8J9K9QCeh6Q zO(2f2+FbJW^y`29R8TL#B&OXE5QOw44f@s@CVy?LPlNxpTA=a z-#^%KzJ8@I5^nMTGo^BYY793@l;94Ew2iKFy77cqcD8Rc!`md^>y>5RC@G4Z@Acr9 zc-E+UTlua9=l6_fhG*I%N4iZyT1_{KAML)Tmbb0W-o|exbD=zfPCpg_;!JjtLb{4j zh;Ymxe~F-=rV33=WT%A@PoQc&ha;+jU)1|pfU z9u(k~zSc}*1JF9^ynkT=*Vjc!ycd)luSr>*W1wpKTrNWn)8PH$QUop&?Zx`FNsBQ5 zOdh4cahN_<;1NBx>`p(dE9@grVm5OENN)h(y}STY04ZoA6+}pyAlHKzEb7fAbcjPe z6krD$SUyy;vnE=&F2Io}2#5MEj137Z*P^&&>rbV``|tyOoS`_x5=(=DDEiBF39OKu zGamr<8Qg~y{Xhah`UebLMDl-v8G?Ncukqv(XKhF!R|*mU>HDj#X;+V zL%1x6SFPce;{KxeJ5f?N746GTfITmDL zS`(^fHWfK;S(+SG>x0si4ZAptE2DaD{Mn)VtC#)L3&_XI!Sjo`F4>QF9V3!?TB$$a zaeKO{my|#ZA3NokR4agN<3?)l^iG5SF_GKds~Un)WvP=O!KD*DxCBaZC#N5z&MEH{ zv8~l;k$x0&0&y$u2~)O=vfi#CqLGK_{_FNCr8Hs`0yrO@9{%)pao>B!()%_&;0b|1 zY#|&W#R*3fMxV>Ufle_5g+q*CRB}Mx80)-bNO{X*SUV&jK>DHW0gF-*#4ijWxw!^2 z#0HQ6NIqxD;FV`_E_`Z{6Qh25lY*mrE;iS>C$m~rmWV~cPSo33m<58u#?rPDJ1z~j z)cVU)W*Rj?{!N#Y#){(tt1|P0O3L%apEuX9MUq=pY5-9)FzR%q*T5h_3Q12Ug z-SlvL;k?5~hp#}W8Ced}-i&ecL@JA%_i-)p$#s6Zo#W_QL>ak%TNAj2H=1PN@g^J*3e&}9upBmiAcVVquPG505);F;@>^1mh`qS0H`32bIZ1UZT zVQl4t{`KbDwvo!ME%^deJ{W`vGqguto&>5{Ej07nOBLmG8fpbNm}zF=U6lGm<%Oz= z?E1aH&%{(m6|LQ1wc@kIX##Yytnkz~RCoKnFjt!z2Q&>#JY{7vO1Pwr8A$Xv9!^u5 zV^c&FDYEwhgl&jo#bwm1Ttt$;!9FOTcz*!(#ryY)_}5kfrj5RVHtQ&^Z_edfOn2^O z%I5_=?`F$RYXgM8nYFULbiUYkG}histm*AnF*6-#aDJMXa13PN)TL7n0rqr>H`s7; zU&TXgvDJCL@|ixP;q;d||Lwbxy#eTh>E|M1lXruIAkyU-s&CMs=3%_vTvoj(m9}K| z{o?PKU~B#4a5kQS{!{{(*9Eaj;=AX?TIm30yfBs7%GqP$MecZM+@w%$6ltfAtHj5G95QsEW8}yAMLH~{DlauLM#I%4cS9| zxhxW>;r3Q&C2+i+99n>n+?Qdwfe`K5&F=s_5K_Iyy0CxAr@d>k_a*r3#^^q%n!PW^ z81=u3NATf$bCT|A9GV20^10v4C5)5W=iIwEyy!RBJ@csk-LBHmf;;1243>XC_dnP5 zl0H=M7Ap_<3tTh*DR+OHSo(Trpl&E$i2dWONhpM(kYzsf&94g z8sc=4pb7W>;dW>Bl5uDIRC(&*<)I36d;5p=2IWn57Ctz&ZnWBfKYwDHesnp~zj9Py zB6ZkYw&fvnW^i(K1z8D*K(u;c4x((dPBC=ZvmT!=WIH!|RN$gwdRr-Ibpl3*K4F_X?+y1Q(KU z^T5l_!+okDB7@NR(hz(q8E2L)T0eGQ%`levIBvg|efv@Gy=JBAHkZ*0!ozch&3xy$ zx_h{PuQxO9=cq?SAarbYirrA@K4+xC@>EyKuJ`4ciPV1wAl#M^lD3M+29jG=la9}EMBXz~iqS6o!4lxR?DQS>KTHtnL62$X_OJO{dD`pU+?$!h`uy=^ zjQl1C&L6|BbQ*#2xI-G6{r=;1HfJw)Cr8&CeND501BIB5K<`vYVTuM30-FRZI1f*` zrLug(IVnFr))e?Lj`TWxHH%K4kVP6v>Hr^OUZIdb0Z#;|;2`9IZU`G&Blp6tq9 z6-e+HFHN7JJenGlwQSb>Cku5vq?m!cf2PeE4vUstMV{WC(4`-<6}AX<@Y7lvWri|T z__UUw&t(I?&`<|WoZdBf^Ydj6V%{4})4b+xyiXlu%a7Ho4f3CGd6pLDQ_K@5KjCdm z#XaE_8=+=8o-#&~c#tul^00ewAO{KodNS8gJG!6AjcW5OsCyZat)B)}5yi;|q>Er_|< zx(M=K?#v>nTIfciD1>f_hcop{?xA65pN2?bcF0xhA~J*r=!?=~CAjV?383&b9f;t3 z;RuEL2lyw1hr~|m0%-;?)Bo2g#;$0kt$K3oUe(ZRT&NZ|g<`j56+Y*%T9Ifm@bJrH zzjPXrdr8agG5NkNc;TgJLy3!q%9eRZwWv|{ODdYW(Zo#r6u8PXtx36kN595z6+sNo z`d01Lv2&cAPAM=PNcTwp=H&3#A3_87;-~oiL?Co*-c(^d9)mNQyYMiOec$9%a>|{l z>tt#DFmv&+ zGIjB=ba-DrdfLBwTAaSOcE~`AzKp1>LX55=0ZCDnHW5)=K|_xci_VfCD3@MuQ+JPT z3$GT7MN@z3DmPOd-(SX0SL-STQ(C>O2D^x>T2op(z2_gwPZv#fH&YznH_A`BqNuA# zUJs||1iR#QHi3sL>*c3RraC~Yh5J*sDoK?^Jvm+Wr`bo-mq#sv4!YSz1)$gaX|Z=# z&;4wd9`NPk@)3Dm$yJ(4xF#mVBJ)EzZ*le{Vtb>w>d^Akaur3-yXtaY&bYmHK0S%U zyYM0560f2HO9nS8V{VR@=}Rv5am-%7^mc+WHTrGdiHrh2?O+t!D8;Wu^&LI-2x5af zbpf~bz-fw4amhw6Z`#P_dqY>^r2{Y7015W~Lw~S_^7~!^VtWktS%zbm&GEsA@I5G5 z3w?5$-mxCFBqSa<95w<$psZ)IA{fkX^MW<_uUIl2sS6I1hz_OC;N*j*>3W=C6J4~0 zU}ONYQPdq9Ay^PXyk61C0xaj;{M3>37OLdv`DJq*)$K<1nY0ouxWD-LdDwdTnVTLQ z3{12{ABs-YF^+d9#($G|y!&Yhyjv7~x+mJ-9Tfy#uo;A~4Plu=L*-LBE}2bHC}@kZ zA1<+x8@Y)d25wB*nM32pE}EiIv**Q#!eWc(5C<4|0VYmLwCK zKi#^1RsdBzUH{;8kTgu~cT10rNjG&%e*&cG?iBRRpX2F+f*%pcTF0WQr(SJJ_jREL zy#vc3%yu%+cOEwBF5(2)t?pWnktBCe>4P0@x2Zx#{PtpG7|ahrN3;RIIHx0p!d6IQ z7dzBxIcZjoHD;lDssmwz+R1t%yyu1J$K>g~Au0eM+<{(^sz*tXJg(|G>1C_;d4xXc zZe_2;W^j#Aj$|=wHN<&^j`P665_!3;$JAmMZMLdqZ6 zYb45rc-MC*;cV?3#&(nDRk~^6v!cz60mJ+0`toXaNJ*%d7k~LcqXEmhaj(m2M=5bZ ze!T$|6e;}!T`3PeuT&aXSECk$Y5wKl2bTAOgD<568l|uzCv;2!rZnjrcnoCBFVIRN zRcrkhW_Gn{2SsnbB*?9`&0n`@&}e1EgN5YLBPl7GWU4#Pt?3=&x z`sSO;R9@;FRl(`19{*y}rR2k_h@s_^Nu|0K(H1I-u^2EXac`l*CGyd;IAbeBB=>o; zD4-4xLxIy$_+Ud?0l)xYh%3n<0Bd>y_7$B~rwW<6s?a_T+`C4q^Fe3!bOR32y)H*a z>x-_xx8YIc#939zR0ET2NncLLm4`BuM`0}sku^1_MC(IXC2+~({#YWWQ=EX*4osJN zsaQ^OgRc;fojN%=0z3WX26dj=4}{~6FE}eQ%OjyykeBx1|9PRt`{R>gLg_b zz?74ogG09e=r%Yefh2_V_yjlf3@vFpZ7!G&zY48{vo=O9P4Uw|P6 z(9~{;#R11wbaBW5JOJjt6QSkV%W9uB3dDvYp$pl4;yiqz3*ifxb6LvA8fBzo-y`vb zhA@2Z9pS2%A zqKS;3uNxbH7wcGU*>%WP1I8Dofj(L zaMWOw07@C5Jcw%~06GBO|A7iTREeW&PH@cwt^>A>OrhOsxu`ZHBr@wdwAp{Tus~8i zNiBgmGZ~CvB0OG?Y~g7zJ2;=-{lm{?Ji0Yl4qb^5p{Q^H`?xz+If-HyuO`9WG!-?) zCG0xGO61U05ioL!AW219jU=x?wSO=`NCBrvhCz-ABYR`jmtg(5CXa#59UZlGWXh8xH$j zEjoVfj;cga&o+|eigk7_{q#eRW0Q^3B5FG%!|c{|3iGW-&B4t{5q|xnB9Kc5pKp6 z574q4Tz`#{=KWYf*_8fD;xXTY{ZoWTLY)HxY5t$R#cYWy`TidsdEYmaG-QSenL9$l zkuQcyUGJ^?Rf6m9LXEDYVsHrU;|@i`T8xy1lXQ4UIGQ~8)#9o!1_AnUqjT?&<%e># zd<_~>Z>P7iDvza@QhZV#q5aNeYv7c`l%I1K`Xw({K1ZZ;g$<9uob@`_w@8x6bj55! ztH<`#_MWNCHpFw_z2oJq*IR{}fEEARd%5o36hwP(*-a!nYV@R%MamyuH>L<2@MSZz zm=%i4P*}oao|gPsYy@Z*&%p7yu0P12NU!1`0CoE_;=nhCWT-mvJ|<{+d#Y zjytnQo@sCqVcHJfLap?~$hIZh@2j;JQ19Knpm>UQD+D-%{Q2}>_0S3hG=d~g=d zZ&ibOEPg{y8RSVTR3Iykk9{RvAYAY{q-V^f%qr7FQwM=Qxo$Uy;pYpU9DHyirzGR3 zuN8?#y}g&aE9WE%O>~wv0=C(wM%1j3qnL22sOOZjMsA17dNvIz*tt?Iq!Hv~1*zXE zvuibk`nbv33GtbQAcREm*t@|6KtdmA3eL$j*F4_}@^`=Pkw)nF-QU-VV3hZEqykOY z&tY$QJ^k|j_%jH9M?rwF{a>OSG_wR5y&tv=lzB*ELg*HyTOJGdfcT7NGOA2)atBq{5O$Zm0rH}Jw!`o*hj*$LrVw;-b~ zhWQ+2M~7~sniFLQX@?*5MXn#P>@^hheh)MNFS_hSk-S&U-fDS86g-Ci;#?KGd*2<; zR*fpu+1pLY6ucW3v0-JS<0sBSO4899j+2Uu=Flynw!?9EaLch;jbU}od62z@Lmv2F z`Cm<;d8uE*Y{P7m)HB6iEuI&wby@!Yn0>zSmq72(Cn(xKH0~30?!*x z&ls$h_2ckw!m9JX>kaa^Z;b&&x`)c8SMF1W1wGYf;8bEp<)I5{@Ybba%CZCGtX`8V zy%_O6X_Ib3g@S6X!b|4&Z@!0evJp zJ<6Y`AlutBs#$a#5b`bL71Y`K--|Yy%b>mS1QZy_+N~zHT*NjK1`svWng}NHUwSs8 z&kvvqht#NQbxmkh#*}_(lA(V!Nm-u5P|y2Xi;kBEi<0(C+96{9Kt{)+3;RND?m>D) zdSnktWC$koPx#;G$T=!YTrikl%f2X&I_2?R{M{KkeOmxgS2RvDm6A1Cm1e!Jvyb@b z_{h~*aW2@YaR~GKbT6^*9g2_<3XYn4epLUg25muO%JW|U9tp!x#EouUly-uD|7{}) zj~`PK-0+6&Vve+)0Wil{5;X(^00{>9=`8XqPq!#3R>-1^X_}zO$nF=s=vOz}JU~S( znj8T{>8Px~Pv~?^n=L*!dVtxs2YR?!uMsDU(4+n@?}%dIq?{+ z*##|3jtoh?Pf2qu+jK%haiT*cAi=LIcfT^BFRV|_=j%OpA^Pp!r0GA_ zo1g}xO9D3cu>mx=_NYSe2`{mYH-FqsZCz?K0D&h74sHfc7L_7NHc@OHJ~Drw5B4lC zjDhw$Y;4AqZ-A*4PII+-82J|XmdF=Ip*skW&}QaRK&oJe zIx870JFG*gkmzp!NVo(2HRQ|6+|DoU?r*=zmu!n#XX@$=>F*PBM-V5dhuqbQ6Wdm8 zQ1p0~V~yPr6E#P%Y?BA)xovUe?h^DxSr%v9{!;vu;4Okv6>j?jmTle`b3HHvEgcjg z_)adgl&rRNY4A=IKosB!T84VPW^A!k_|F=vsP{5GwRd_FVCul1i{(cEek7EjlW)^1 z;wR5PBG=wi1vR=#Y*Jy#VCSP&w+ye6j855a$6*N!K_~TYrKyv>weJj24U!1^eN8fn zfk1Bo^H!M@8Q!d=fa!+@Wl#9?IivDuD=#^*K{|6}+?n3&I)CLmZe#9Dyc(`T?0sqo zih3GFes*NEIf}lb&t}%$mZ$z9)f+kxpe$|3F7|J@Fe(bqiV$l-X&eHnKUs8uEeIhe z1_2D}w-KVEB0Ll-7(fxA__@r{mNZMe*Cg90^dr_fe;3NYJr}$tgMe7Dxmq!Q{rHt# zLHzT5p;%3xosT2)%QGD`c_g)%WVnAsze{Cc#EPB4jC%n$3iT+@Eo|Wd!nPiRBdRZG zMooqj6vOrb2`DScQcspWYI5{*_?gV5ua{|vPY#Hko432omqSaW?Zb=d53K%(;}wQ$ zE7^iflf2)Y{xH1H!1>|uCXXN|$TEm9W5O{&8qB~{=lJ181RZLdR2DR?+zWG=*F5iR*m2q4UI9x(I4Kkoe|nO- zba-g^I&^81XkP;>(!Mbzmlo224=ICgV&2^k<;$I@DpAEAS{jS@OXJR-6*ZefC#PQo zE(F$u3_vBL1a629paM{R>a6G;ldX@C>(tjox&5m}(NP;RdUTK zU?@Z0oCIxF^$iD_kBd78hG;G>p{6bIDA*cNd2zJ7MeJ;H62y2c?93PmoO70hk#s|W zrr$vWNQ#$I4YdN$tQdyySpb?pzm>KW`GQRJcnIbO8Np3WrH}!g=yhTTbMmQ)_wDI% zVH+P)xOj>=nMzAdIBVpsQoop3Ksl*AvrR^fpIsS;3a)JL!4JAD;|A1o@i-Em-!+hV zI*co*=72n;Jka_|J#8%qjoW^%8;WQ752H=#A@JY>7SXiF^rqvfcxmAREz2UHPud?m6{4?DWR`Fp+_<%#{w)y zU!-RoQb-sNQ|Jy%2u!HwQ~%k@nJ(=QW9u+Asb{Het@(xak zEnQTA&PoolDTKjy&;cOh0@NWO(KmZL+E^1`yrJzrF!Wi(!koXyq@bR$I-5v4FF)g@ z*p)qSHCg_x!*I2?Jg>iz8Fx{svY+mQ^M}F~=%`JR%&$NDX$700A<{f3CQW_Z&WhZU zg^5}##Rz4LY6^y`3{Uo1fTMy833Axw-)*Y97nH|V^yaq~9Q8pgICna1bksjQf^h2k z-3QI_3dmHwYVJo@7RT3aFI&;dbj|`*{b%!BNK95yq!R;76|Wx+BGF#c0Qtm%(j~LhT?7SpZxBuFv}u zOma!0rIBQi&!iKoZ*pg-WtAQpLW}EN$sh*ndYGhOn5A>eS?YAN5!-3wn_UUI8wXP-Wb+x{FH$i?D2ztbtgB|dnRc(TvD)Jsv z_SHN7HdOIaeh<0?fsw-U($cgzzAzLXA!N0coG$Ykaeu?yp}x&Skluv=#RExDr?`3xpXc@5!J;5e;T@5mj|OzcVyC1punhJ*z^resDK8n_)dQKo0LTS>S38IietcTygVp3JB?i?GnW_WN-Ox6e1 zCxkV`1sw$p^%K`iH&uZl519npeXFYZDYPP)wcJuwdtlwwkT^fi-efsAFuG7pgj#6rgs zgLHMNDq`!*yGJwp=S|r$w=vFgco=kYN$& zaOFgd$`V2iYKE; zY3S5veXw$p##4oL{I#?J>0iNr>K19A(|qCWy}!pOSAsrW z-|vr*&9AD=+>xr%xGGs=2rXD3CkZD7ZGw&^YBe}&9wiI$3ZE5T=6Lz{2x z`tgWYhEco|#z=byR->t==TDAE-_UNH%}4Tu5K3CRf_pI^wGq+gh~+Z9kz10JNsjz* z!9MQjn>iMLRm^o5D~!?ZA#)+rijpDXu-2#oQ`e7n$LBT2w2Y3#P}c`i+QwCnP9B6y zn~Al)f)98>u0a%to1rmJZ}6Yqq)z)g%%h0~Mwkgs!N zIsG){^0+d!CkHk%kwo$5JZhdiE8b?&Z9p~vW6>Cu0<=D_NbQqX0JjA_!zR4nHFFTL zK!N?0W~jS+LoMH0htK3%7}Tei?!YuFV>DGE;-x-)4v$nMc{v6|%=i^5*m5h z#(Qu1H8AQxksD2KN#Sth@Q4arbD}zUjRUr_L;tJV?#0bdLd)Z8trnj`0h7qBJkqPM^4Q!`vSaj`ppbQkc%RI2@or9!ilp3!~$ZO zn96u<4F&tFFMVhgDkK-cg*>W>V>xwQ#9`?Vk?MJ^oj0oO!2bhf4a+pEKUZz+HV#;? zk|k!JZA?||?)PkMp)+B(qIW&hBcmDZ{HQl*P z{B?NP8s1};G`^R~#bKUFzAl`P$0}Mu8xaXusAYtNFTRAoUu5A&mL7pokRM%Cltc0} z#*J*_Sz%I|04@L*^L}BahBaOZI>lwa{(>#NQ6*R1uF&tBd;HW~0sY(ARtJN3)AezK zRbsLM^yXYE0UDPfBuzEawRRJ_(vVXhW^M!Hq?%SY|p(ofHWzqiaM_B@x(u`4B~$p8y8f#-+;l2b`!ZF7`3P6rp3>&GPEI=H`# z<3fD2*IWt)tz6&LEoi8n%@>q#wTs&E9n0Edptu2guPTvxdxtvnx0e)ir(t4>-mmxN zCc9NIa?Jj%ZL+e(KyqrAOV<79OT}5vS>dN2i}(mfAlU286=l2CJ;6P;iM-YEU`8hk z1C7ev++?YVSffl6fd;hsnFO>>_tHV(du@lJ*EW666NYGGvx3fTy+^3pRZ%gX;V#$r zgajcSAa+ZzA9-9_H$;fjL@Gd0>dLjVGAYC(5jL^Ki@EzCtrHeCUFv6m&+XVnK~gLL zEr1piScwkg3tVfS8~oI3!OmKd=ZX%FjF{SNDAV7s&u&T{`nPG-#E%z|lh$b=m{j6| z9l6EowbvX>N-XK(R;gD1_ND|~aOsibJ%I(}*|EGhWHrY0N|ISP#2EK9c{Ml&MPpSbwl|y+sK8uf$@7}hR_l@^YVv|>X(u~VuXHZFb zARAW=JAfO&&5#v9Y)iK>Ee)p4H5nBZEg zUEJ)Q?BnT^Xl+liI(bLl%UD)WL)6y~M}}MQX1d=HeRC%nLp&tJ#oTh{wsn#56jZ?5 zG4^6(!#!Z~S7u?7$A3P=B>J{qS2U^Fw6n` z=Y2L0U$sf)5T|BU-#kk)sv}`UA~esZ>tW z3|EottXb})k}!lzXX%dPIkjS>M%TFuCv*cpK(;S_1!rJ)Od#9#l8+~VkPyf=gAH*# z_}r8c*|r*=Y%~5Ns-h|_<#ihI`M6)z&cnW;y~o$vB17#H8DIHSwAT>2-kEQBBb&ZUPg)tiDdK>&K9au1YnoPd3feWt#7 zaKWL{I_GRLEoRs5K%k7We4D&sb0It6r9y2_Sl?ikaZp zkn=8OARWT}xawgoVHLck+S80-yHy#fA~%tCft{PfB#QWoWaccR^1%tW4OP@I9Q#w2~kr9GO!N5eL3ZshtE>WigX28T~W9#bIwwzI7V+N`( z^)y9{XY9ddqXuH9Jj4HN-}?7nd7R$++PCkX;p{848xLv3eCo5jOoF}eZ<^B% zFPGQ+3p~6TwdA$B2%1~AscQR@X|*rp6uzXZVG&rI#i?Zh^S%?39`0LVUIq19-lmg% zfPkvBej5NBISvG1?E%<^Fy`}Zj3fiL8;aPVF(GH!K^vk)29j-={9m3d^lECf8;-U{ zWd3%0JLBe|Gwu}T!NsojSNRyU_ypekyMM{qlHc8v!WHkxwn4YxPw?`koy{;Pqk9lQ zT$SDrZwGWd0PwzS2Lx_1y%Q!liBF0DDB{)mk$uw!7ff1H=K9^>3pWbNIhdGA9dj#y z1wBL^Vq^ugFc}wY;Dp8KOF}QVw9M5A)%Z{V2FE`>|-$VR;;OV#BDzsDqJL zCG(?pD&o|4A1TR7{@Udytw#v$3EmKjEv?$YtWSAI&PL#F{d0e=Nm>$ zCA-!6$IcC2hSa*fDA!b)d^5g*%4E$mx+l&QMTuVL{uf0tEq3LVuYCiQYxyIF*w3^m ztb1N>5}|sTzP-%%Wmlssa(x8+>TgsPY|W!^w9!C(nN+X#%TqxTViE3nZIm5m%A|Dr zl1}ki&koRC4`;OL*+rC7N}n6ez|-T-s&0CO*{qlS-jk}MVb9zjdJ6yU4d=8FCB5<1 z68vFHmJ?P~=E-^*`0vj7ZnJuxpAj&#EE-)i$sJt(YjCdh?e(Z0 zw}}Tc@WXcJ%ZKH(2Sa7?Bi(AxG|dY=YaS*K$`f@pVp)B&&RQH79LvYP=@w>NGr!X@ zz=dIQiH`kMp4kkwL?T2S+U(fjdGRC^24x^ZEACl8wvlzW6q$qTfwL(p=nZW`>07xG zy~?mxyAaY;_sdN+lb@uposLGI=8qNoDoPP8h@W$Vl~(%m`YqSoZ&(ZGOo$%WM%(#A zRT4XEb|h|?5FGeWuO>kdcMpgf5R?tz1aNv|0ZLoUfcbUC1}xRu+eoa6q0?~ct(Sw! zhgX_!)c(V7UG?_q=C_$`2qaIZ1oR-|#ljBi-hEWb5C1vyr*6*RPp)UMnPqf`sRd!N z`)aC|!+A&|Z<4%=65r+TNQL3ABw##a=wM~OVS2QfzHpG#ZEF%`=Ve$&%bQiA&NgG+ RO?c=MA;jMto;NH2{{i5^BW3^q diff --git a/x-pack/test/security_solution_cypress/es_archives/timeline_alerts/mappings.json b/x-pack/test/security_solution_cypress/es_archives/timeline_alerts/mappings.json deleted file mode 100644 index 4e5683f2f3932..0000000000000 --- a/x-pack/test/security_solution_cypress/es_archives/timeline_alerts/mappings.json +++ /dev/null @@ -1,9588 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "action": "c0c235fba02ebd2a2412bcda79009b58", - "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", - "alert": "e588043a01d3d43477e7cad7efa0f5d8", - "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", - "apm-services-telemetry": "07ee1939fa4302c62ddc052ec03fed90", - "canvas-element": "7390014e1091044523666d97247392fc", - "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "config": "87aca8fdb053154f11383fce3dbf3edf", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", - "inventory-view": "84b320fd67209906333ffce261128462", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "lens": "21c3ea0763beb1ecb0162529706b88c5", - "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", - "map": "23d7aa4a720d4938ccde3983f87bd58d", - "maps-telemetry": "268da3a48066123fc5baf35abaa55014", - "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "namespace": "2f4316de49999235636386fe51dc06c1", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "181661168bbadd1eff5902361e2a0d5c", - "server": "ec97f1c5da1a19609a60874e5af1100c", - "siem-detection-engine-rule-status": "0367e4d775814b56a4bee29384f9aafe", - "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", - "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", - "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", - "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", - "telemetry": "358ffaa88ba34a97d55af0933a117de4", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", - "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "url": "c7f66a0df8b1b52f17c28c4adb111105", - "visualization": "52d7a13ad68a150c4525b292d23e12cc" - } - }, - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "actionTypeId": { - "type": "keyword" - }, - "config": { - "enabled": false, - "type": "object" - }, - "name": { - "type": "text" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "properties": { - "actionId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "actionTypeId": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "consumer": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - }, - "createdBy": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "muteAll": { - "type": "boolean" - }, - "mutedInstanceIds": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "params": { - "enabled": false, - "type": "object" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "scheduledTaskId": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "throttle": { - "type": "keyword" - }, - "updatedAt": { - "type": "date" - }, - "updatedBy": { - "type": "keyword" - } - } - }, - "apm-indices": { - "properties": { - "apm_oss": { - "properties": { - "errorIndices": { - "type": "keyword" - }, - "metricsIndices": { - "type": "keyword" - }, - "onboardingIndices": { - "type": "keyword" - }, - "sourcemapIndices": { - "type": "keyword" - }, - "spanIndices": { - "type": "keyword" - }, - "transactionIndices": { - "type": "keyword" - } - } - } - } - }, - "apm-services-telemetry": { - "properties": { - "has_any_services": { - "type": "boolean" - }, - "services_per_agent": { - "properties": { - "dotnet": { - "null_value": 0, - "type": "long" - }, - "go": { - "null_value": 0, - "type": "long" - }, - "java": { - "null_value": 0, - "type": "long" - }, - "js-base": { - "null_value": 0, - "type": "long" - }, - "nodejs": { - "null_value": 0, - "type": "long" - }, - "python": { - "null_value": 0, - "type": "long" - }, - "ruby": { - "null_value": 0, - "type": "long" - }, - "rum-js": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "infrastructure-ui-source": { - "properties": { - "description": { - "type": "text" - }, - "fields": { - "properties": { - "container": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "pod": { - "type": "keyword" - }, - "tiebreaker": { - "type": "keyword" - }, - "timestamp": { - "type": "keyword" - } - } - }, - "logAlias": { - "type": "keyword" - }, - "logColumns": { - "properties": { - "fieldColumn": { - "properties": { - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - } - } - }, - "messageColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "timestampColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - } - }, - "type": "nested" - }, - "metricAlias": { - "type": "keyword" - }, - "name": { - "type": "text" - } - } - }, - "inventory-view": { - "properties": { - "autoBounds": { - "type": "boolean" - }, - "autoReload": { - "type": "boolean" - }, - "boundsOverride": { - "properties": { - "max": { - "type": "integer" - }, - "min": { - "type": "integer" - } - } - }, - "customOptions": { - "properties": { - "field": { - "type": "keyword" - }, - "text": { - "type": "keyword" - } - }, - "type": "nested" - }, - "filterQuery": { - "properties": { - "expression": { - "type": "keyword" - }, - "kind": { - "type": "keyword" - } - } - }, - "groupBy": { - "properties": { - "field": { - "type": "keyword" - }, - "label": { - "type": "keyword" - } - }, - "type": "nested" - }, - "metric": { - "properties": { - "type": { - "type": "keyword" - } - } - }, - "name": { - "type": "keyword" - }, - "nodeType": { - "type": "keyword" - }, - "time": { - "type": "integer" - }, - "view": { - "type": "keyword" - } - } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "lens": { - "properties": { - "expression": { - "index": false, - "type": "keyword" - }, - "state": { - "type": "flattened" - }, - "title": { - "type": "text" - }, - "visualizationType": { - "type": "keyword" - } - } - }, - "lens-ui-telemetry": { - "properties": { - "count": { - "type": "integer" - }, - "date": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "map": { - "properties": { - "bounds": { - "type": "geo_shape" - }, - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps-telemetry": { - "properties": { - "attributesPerMap": { - "properties": { - "dataSourcesCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - }, - "emsVectorLayersCount": { - "dynamic": "true", - "type": "object" - }, - "layerTypesCount": { - "dynamic": "true", - "type": "object" - }, - "layersCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - } - } - }, - "indexPatternsWithGeoFieldCount": { - "type": "long" - }, - "mapsTotalCount": { - "type": "long" - }, - "settings": { - "properties": { - "showMapVisualizationTypes": { - "type": "boolean" - } - } - }, - "timeCaptured": { - "type": "date" - } - } - }, - "metrics-explorer-view": { - "properties": { - "chartOptions": { - "properties": { - "stack": { - "type": "boolean" - }, - "type": { - "type": "keyword" - }, - "yAxisMode": { - "type": "keyword" - } - } - }, - "currentTimerange": { - "properties": { - "from": { - "type": "keyword" - }, - "interval": { - "type": "keyword" - }, - "to": { - "type": "keyword" - } - } - }, - "name": { - "type": "keyword" - }, - "options": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "filterQuery": { - "type": "keyword" - }, - "groupBy": { - "type": "keyword" - }, - "limit": { - "type": "integer" - }, - "metrics": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "color": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "label": { - "type": "keyword" - } - }, - "type": "nested" - } - } - } - } - }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "dashboard": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "index-pattern": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "search": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "space": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "visualization": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "ml-telemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "namespace": { - "type": "keyword" - }, - "query": { - "properties": { - "description": { - "type": "text" - }, - "filters": { - "enabled": false, - "type": "object" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "index": false, - "type": "keyword" - } - } - }, - "timefilter": { - "enabled": false, - "type": "object" - }, - "title": { - "type": "text" - } - } - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "siem-detection-engine-rule-status": { - "properties": { - "alertId": { - "type": "keyword" - }, - "lastFailureAt": { - "type": "date" - }, - "lastFailureMessage": { - "type": "text" - }, - "lastSuccessAt": { - "type": "date" - }, - "lastSuccessMessage": { - "type": "text" - }, - "status": { - "type": "keyword" - }, - "statusDate": { - "type": "date" - } - } - }, - "siem-ui-timeline": { - "properties": { - "columns": { - "properties": { - "aggregatable": { - "type": "boolean" - }, - "category": { - "type": "keyword" - }, - "columnHeaderType": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "example": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "indexes": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "placeholder": { - "type": "text" - }, - "searchable": { - "type": "boolean" - }, - "type": { - "type": "keyword" - } - } - }, - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "dataProviders": { - "properties": { - "and": { - "properties": { - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - } - } - }, - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - } - } - }, - "dateRange": { - "properties": { - "end": { - "type": "date" - }, - "start": { - "type": "date" - } - } - }, - "description": { - "type": "text" - }, - "eventType": { - "type": "keyword" - }, - "favorite": { - "properties": { - "favoriteDate": { - "type": "date" - }, - "fullName": { - "type": "text" - }, - "keySearch": { - "type": "text" - }, - "userName": { - "type": "text" - } - } - }, - "filters": { - "properties": { - "exists": { - "type": "text" - }, - "match_all": { - "type": "text" - }, - "meta": { - "properties": { - "alias": { - "type": "text" - }, - "controlledBy": { - "type": "text" - }, - "disabled": { - "type": "boolean" - }, - "field": { - "type": "text" - }, - "formattedValue": { - "type": "text" - }, - "index": { - "type": "keyword" - }, - "key": { - "type": "keyword" - }, - "negate": { - "type": "boolean" - }, - "params": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "value": { - "type": "text" - } - } - }, - "missing": { - "type": "text" - }, - "query": { - "type": "text" - }, - "range": { - "type": "text" - }, - "script": { - "type": "text" - } - } - }, - "kqlMode": { - "type": "keyword" - }, - "kqlQuery": { - "properties": { - "filterQuery": { - "properties": { - "kuery": { - "properties": { - "expression": { - "type": "text" - }, - "kind": { - "type": "keyword" - } - } - }, - "serializedQuery": { - "type": "text" - } - } - } - } - }, - "savedQueryId": { - "type": "keyword" - }, - "sort": { - "properties": { - "columnId": { - "type": "keyword" - }, - "sortDirection": { - "type": "keyword" - } - } - }, - "title": { - "type": "text" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-note": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-pinned-event": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "disabledFeatures": { - "type": "keyword" - }, - "imageUrl": { - "index": false, - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "telemetry": { - "properties": { - "enabled": { - "type": "boolean" - }, - "lastReported": { - "type": "date" - }, - "lastVersionChecked": { - "ignore_above": 256, - "type": "keyword" - }, - "sendUsageFrom": { - "ignore_above": 256, - "type": "keyword" - }, - "userHasSeenNotice": { - "type": "boolean" - } - } - }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "tsvb-validation-telemetry": { - "properties": { - "failedRequests": { - "type": "long" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "dynamic": "true", - "properties": { - "indexName": { - "type": "keyword" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - ".siem-signals-default": { - "is_write_index": true - } - }, - "index": ".siem-signals-default-000001", - "mappings": { - "dynamic": "false", - "_meta": { - "version": 3 - }, - "properties": { - "@timestamp": { - "type": "date" - }, - "agent": { - "properties": { - "ephemeral_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "client": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "cloud": { - "properties": { - "account": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "availability_zone": { - "type": "keyword", - "ignore_above": 1024 - }, - "instance": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "machine": { - "properties": { - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "provider": { - "type": "keyword", - "ignore_above": 1024 - }, - "region": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "container": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "image": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "tag": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "labels": { - "type": "object" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "runtime": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "destination": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "dll": { - "properties": { - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "dns": { - "properties": { - "answers": { - "properties": { - "class": { - "type": "keyword", - "ignore_above": 1024 - }, - "data": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "ttl": { - "type": "long" - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "header_flags": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "op_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "question": { - "properties": { - "class": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "subdomain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "resolved_ip": { - "type": "ip" - }, - "response_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ecs": { - "properties": { - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "error": { - "properties": { - "code": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "message": { - "type": "text", - "norms": false - }, - "stack_trace": { - "type": "keyword", - "index": false, - "doc_values": false, - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "event": { - "properties": { - "action": { - "type": "keyword", - "ignore_above": 1024 - }, - "category": { - "type": "keyword", - "ignore_above": 1024 - }, - "code": { - "type": "keyword", - "ignore_above": 1024 - }, - "created": { - "type": "date" - }, - "dataset": { - "type": "keyword", - "ignore_above": 1024 - }, - "duration": { - "type": "long" - }, - "end": { - "type": "date" - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "ingested": { - "type": "date" - }, - "kind": { - "type": "keyword", - "ignore_above": 1024 - }, - "module": { - "type": "keyword", - "ignore_above": 1024 - }, - "original": { - "type": "keyword", - "index": false, - "doc_values": false, - "ignore_above": 1024 - }, - "outcome": { - "type": "keyword", - "ignore_above": 1024 - }, - "provider": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "risk_score": { - "type": "float" - }, - "risk_score_norm": { - "type": "float" - }, - "sequence": { - "type": "long" - }, - "severity": { - "type": "long" - }, - "start": { - "type": "date" - }, - "timezone": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "url": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "file": { - "properties": { - "accessed": { - "type": "date" - }, - "attributes": { - "type": "keyword", - "ignore_above": 1024 - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "created": { - "type": "date" - }, - "ctime": { - "type": "date" - }, - "device": { - "type": "keyword", - "ignore_above": 1024 - }, - "directory": { - "type": "keyword", - "ignore_above": 1024 - }, - "drive_letter": { - "type": "keyword", - "ignore_above": 1 - }, - "extension": { - "type": "keyword", - "ignore_above": 1024 - }, - "gid": { - "type": "keyword", - "ignore_above": 1024 - }, - "group": { - "type": "keyword", - "ignore_above": 1024 - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "inode": { - "type": "keyword", - "ignore_above": 1024 - }, - "mime_type": { - "type": "keyword", - "ignore_above": 1024 - }, - "mode": { - "type": "keyword", - "ignore_above": 1024 - }, - "mtime": { - "type": "date" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "owner": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "size": { - "type": "long" - }, - "target_path": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "uid": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword", - "ignore_above": 1024 - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hostname": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "uptime": { - "type": "long" - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "http": { - "properties": { - "request": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "method": { - "type": "keyword", - "ignore_above": 1024 - }, - "referrer": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "response": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "status_code": { - "type": "long" - } - } - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "interface": { - "properties": { - "alias": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "labels": { - "type": "object" - }, - "log": { - "properties": { - "level": { - "type": "keyword", - "ignore_above": 1024 - }, - "logger": { - "type": "keyword", - "ignore_above": 1024 - }, - "origin": { - "properties": { - "file": { - "properties": { - "line": { - "type": "integer" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "function": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "original": { - "type": "keyword", - "index": false, - "doc_values": false, - "ignore_above": 1024 - }, - "syslog": { - "properties": { - "facility": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "priority": { - "type": "long" - }, - "severity": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - } - } - }, - "message": { - "type": "text", - "norms": false - }, - "network": { - "properties": { - "application": { - "type": "keyword", - "ignore_above": 1024 - }, - "bytes": { - "type": "long" - }, - "community_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "direction": { - "type": "keyword", - "ignore_above": 1024 - }, - "forwarded_ip": { - "type": "ip" - }, - "iana_number": { - "type": "keyword", - "ignore_above": 1024 - }, - "inner": { - "properties": { - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "packets": { - "type": "long" - }, - "protocol": { - "type": "keyword", - "ignore_above": 1024 - }, - "transport": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "observer": { - "properties": { - "egress": { - "properties": { - "interface": { - "properties": { - "alias": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "zone": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hostname": { - "type": "keyword", - "ignore_above": 1024 - }, - "ingress": { - "properties": { - "interface": { - "properties": { - "alias": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "zone": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - }, - "serial_number": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "vendor": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "organization": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "package": { - "properties": { - "architecture": { - "type": "keyword", - "ignore_above": 1024 - }, - "build_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "checksum": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "install_scope": { - "type": "keyword", - "ignore_above": 1024 - }, - "installed": { - "type": "date" - }, - "license": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "size": { - "type": "long" - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "process": { - "properties": { - "args": { - "type": "keyword", - "ignore_above": 1024 - }, - "args_count": { - "type": "long" - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "command_line": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "entity_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "executable": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "parent": { - "properties": { - "args": { - "type": "keyword", - "ignore_above": 1024 - }, - "args_count": { - "type": "long" - }, - "code_signature": { - "properties": { - "exists": { - "type": "boolean" - }, - "status": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "trusted": { - "type": "boolean" - }, - "valid": { - "type": "boolean" - } - } - }, - "command_line": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "entity_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "executable": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha512": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "title": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "pe": { - "properties": { - "company": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "file_version": { - "type": "keyword", - "ignore_above": 1024 - }, - "original_file_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "product": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "title": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "registry": { - "properties": { - "data": { - "properties": { - "bytes": { - "type": "keyword", - "ignore_above": 1024 - }, - "strings": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hive": { - "type": "keyword", - "ignore_above": 1024 - }, - "key": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "value": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "related": { - "properties": { - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "ip": { - "type": "ip" - }, - "user": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "rule": { - "properties": { - "author": { - "type": "keyword", - "ignore_above": 1024 - }, - "category": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "license": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "ruleset": { - "type": "keyword", - "ignore_above": 1024 - }, - "uuid": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "server": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "service": { - "properties": { - "ephemeral_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "node": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "state": { - "type": "keyword", - "ignore_above": 1024 - }, - "type": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "signal": { - "properties": { - "ancestors": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "depth": { - "type": "integer" - }, - "group": { - "properties": { - "id": { - "type": "keyword" - }, - "index": { - "type": "integer" - } - } - }, - "original_event": { - "properties": { - "action": { - "type": "keyword" - }, - "category": { - "type": "keyword" - }, - "code": { - "type": "keyword" - }, - "created": { - "type": "date" - }, - "dataset": { - "type": "keyword" - }, - "duration": { - "type": "long" - }, - "end": { - "type": "date" - }, - "hash": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "kind": { - "type": "keyword" - }, - "module": { - "type": "keyword" - }, - "original": { - "type": "keyword", - "index": false, - "doc_values": false - }, - "outcome": { - "type": "keyword" - }, - "provider": { - "type": "keyword" - }, - "risk_score": { - "type": "float" - }, - "risk_score_norm": { - "type": "float" - }, - "sequence": { - "type": "long" - }, - "severity": { - "type": "long" - }, - "start": { - "type": "date" - }, - "timezone": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "original_signal": { - "type": "object", - "dynamic": "false", - "enabled": false - }, - "original_time": { - "type": "date" - }, - "parent": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "parents": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "rule": { - "properties": { - "author": { - "type": "keyword" - }, - "building_block_type": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "enabled": { - "type": "keyword" - }, - "false_positives": { - "type": "keyword" - }, - "filters": { - "type": "object" - }, - "from": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "immutable": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "interval": { - "type": "keyword" - }, - "language": { - "type": "keyword" - }, - "license": { - "type": "keyword" - }, - "max_signals": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "output_index": { - "type": "keyword" - }, - "query": { - "type": "keyword" - }, - "references": { - "type": "keyword" - }, - "risk_score": { - "type": "float" - }, - "risk_score_mapping": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "value": { - "type": "keyword" - } - } - }, - "rule_id": { - "type": "keyword" - }, - "rule_name_override": { - "type": "keyword" - }, - "saved_id": { - "type": "keyword" - }, - "severity": { - "type": "keyword" - }, - "severity_mapping": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "severity": { - "type": "keyword" - }, - "value": { - "type": "keyword" - } - } - }, - "size": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "threat": { - "properties": { - "framework": { - "type": "keyword" - }, - "tactic": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "reference": { - "type": "keyword" - } - } - }, - "technique": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "reference": { - "type": "keyword" - } - } - } - } - }, - "threshold": { - "properties": { - "field": { - "type": "keyword" - }, - "value": { - "type": "float" - } - } - }, - "timeline_id": { - "type": "keyword" - }, - "timeline_title": { - "type": "keyword" - }, - "timestamp_override": { - "type": "keyword" - }, - "to": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "status": { - "type": "keyword" - }, - "threshold_count": { - "type": "float" - }, - "threshold_result": { - "properties": { - "count": { - "type": "long" - }, - "value": { - "type": "keyword" - } - } - } - } - }, - "source": { - "properties": { - "address": { - "type": "keyword", - "ignore_above": 1024 - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "geo": { - "properties": { - "city_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "continent_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "country_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "location": { - "type": "geo_point" - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_iso_code": { - "type": "keyword", - "ignore_above": 1024 - }, - "region_name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "type": "keyword", - "ignore_above": 1024 - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - } - } - }, - "tags": { - "type": "keyword", - "ignore_above": 1024 - }, - "threat": { - "properties": { - "framework": { - "type": "keyword", - "ignore_above": 1024 - }, - "tactic": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "technique": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "tls": { - "properties": { - "cipher": { - "type": "keyword", - "ignore_above": 1024 - }, - "client": { - "properties": { - "certificate": { - "type": "keyword", - "ignore_above": 1024 - }, - "certificate_chain": { - "type": "keyword", - "ignore_above": 1024 - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "issuer": { - "type": "keyword", - "ignore_above": 1024 - }, - "ja3": { - "type": "keyword", - "ignore_above": 1024 - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "server_name": { - "type": "keyword", - "ignore_above": 1024 - }, - "subject": { - "type": "keyword", - "ignore_above": 1024 - }, - "supported_ciphers": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "curve": { - "type": "keyword", - "ignore_above": 1024 - }, - "established": { - "type": "boolean" - }, - "next_protocol": { - "type": "keyword", - "ignore_above": 1024 - }, - "resumed": { - "type": "boolean" - }, - "server": { - "properties": { - "certificate": { - "type": "keyword", - "ignore_above": 1024 - }, - "certificate_chain": { - "type": "keyword", - "ignore_above": 1024 - }, - "hash": { - "properties": { - "md5": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha1": { - "type": "keyword", - "ignore_above": 1024 - }, - "sha256": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "issuer": { - "type": "keyword", - "ignore_above": 1024 - }, - "ja3s": { - "type": "keyword", - "ignore_above": 1024 - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "subject": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - }, - "version_protocol": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "trace": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "transaction": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "url": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "extension": { - "type": "keyword", - "ignore_above": 1024 - }, - "fragment": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "original": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "password": { - "type": "keyword", - "ignore_above": 1024 - }, - "path": { - "type": "keyword", - "ignore_above": 1024 - }, - "port": { - "type": "long" - }, - "query": { - "type": "keyword", - "ignore_above": 1024 - }, - "registered_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "scheme": { - "type": "keyword", - "ignore_above": 1024 - }, - "top_level_domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "username": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "user": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "email": { - "type": "keyword", - "ignore_above": 1024 - }, - "full_name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "group": { - "properties": { - "domain": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "hash": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - } - } - }, - "user_agent": { - "properties": { - "device": { - "properties": { - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - }, - "original": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "os": { - "properties": { - "family": { - "type": "keyword", - "ignore_above": 1024 - }, - "full": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "kernel": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "platform": { - "type": "keyword", - "ignore_above": 1024 - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vlan": { - "properties": { - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "name": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "vulnerability": { - "properties": { - "category": { - "type": "keyword", - "ignore_above": 1024 - }, - "classification": { - "type": "keyword", - "ignore_above": 1024 - }, - "description": { - "type": "keyword", - "ignore_above": 1024, - "fields": { - "text": { - "type": "text", - "norms": false - } - } - }, - "enumeration": { - "type": "keyword", - "ignore_above": 1024 - }, - "id": { - "type": "keyword", - "ignore_above": 1024 - }, - "reference": { - "type": "keyword", - "ignore_above": 1024 - }, - "report_id": { - "type": "keyword", - "ignore_above": 1024 - }, - "scanner": { - "properties": { - "vendor": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "score": { - "properties": { - "base": { - "type": "float" - }, - "environmental": { - "type": "float" - }, - "temporal": { - "type": "float" - }, - "version": { - "type": "keyword", - "ignore_above": 1024 - } - } - }, - "severity": { - "type": "keyword", - "ignore_above": 1024 - } - } - } - } - }, - "settings": { - "index": { - "lifecycle": { - "name": ".siem-signals-default", - "rollover_alias": ".siem-signals-default" - }, - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - "auditbeat-7.6.2": { - "is_write_index": true - } - }, - "index": "auditbeat-7.6.2-2020.03.20-000001", - "mappings": { - "_meta": { - "beat": "auditbeat", - "version": "7.6.2" - }, - "date_detection": false, - "dynamic_templates": [ - { - "labels": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "labels.*" - } - }, - { - "container.labels": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "container.labels.*" - } - }, - { - "dns.answers": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "dns.answers.*" - } - }, - { - "log.syslog": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "log.syslog.*" - } - }, - { - "fields": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "fields.*" - } - }, - { - "docker.container.labels": { - "mapping": { - "type": "keyword" - }, - "match_mapping_type": "string", - "path_match": "docker.container.labels.*" - } - }, - { - "kubernetes.labels.*": { - "mapping": { - "type": "keyword" - }, - "path_match": "kubernetes.labels.*" - } - }, - { - "kubernetes.annotations.*": { - "mapping": { - "type": "keyword" - }, - "path_match": "kubernetes.annotations.*" - } - }, - { - "strings_as_keyword": { - "mapping": { - "ignore_above": 1024, - "type": "keyword" - }, - "match_mapping_type": "string" - } - } - ], - "properties": { - "@timestamp": { - "type": "date" - }, - "agent": { - "properties": { - "ephemeral_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "auditd": { - "properties": { - "data": { - "properties": { - "a0": { - "ignore_above": 1024, - "type": "keyword" - }, - "a1": { - "ignore_above": 1024, - "type": "keyword" - }, - "a2": { - "ignore_above": 1024, - "type": "keyword" - }, - "a3": { - "ignore_above": 1024, - "type": "keyword" - }, - "a[0-3]": { - "ignore_above": 1024, - "type": "keyword" - }, - "acct": { - "ignore_above": 1024, - "type": "keyword" - }, - "acl": { - "ignore_above": 1024, - "type": "keyword" - }, - "action": { - "ignore_above": 1024, - "type": "keyword" - }, - "added": { - "ignore_above": 1024, - "type": "keyword" - }, - "addr": { - "ignore_above": 1024, - "type": "keyword" - }, - "apparmor": { - "ignore_above": 1024, - "type": "keyword" - }, - "arch": { - "ignore_above": 1024, - "type": "keyword" - }, - "argc": { - "ignore_above": 1024, - "type": "keyword" - }, - "audit_backlog_limit": { - "ignore_above": 1024, - "type": "keyword" - }, - "audit_backlog_wait_time": { - "ignore_above": 1024, - "type": "keyword" - }, - "audit_enabled": { - "ignore_above": 1024, - "type": "keyword" - }, - "audit_failure": { - "ignore_above": 1024, - "type": "keyword" - }, - "banners": { - "ignore_above": 1024, - "type": "keyword" - }, - "bool": { - "ignore_above": 1024, - "type": "keyword" - }, - "bus": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_fe": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_fi": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_fp": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_fver": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_pe": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_pi": { - "ignore_above": 1024, - "type": "keyword" - }, - "cap_pp": { - "ignore_above": 1024, - "type": "keyword" - }, - "capability": { - "ignore_above": 1024, - "type": "keyword" - }, - "cgroup": { - "ignore_above": 1024, - "type": "keyword" - }, - "changed": { - "ignore_above": 1024, - "type": "keyword" - }, - "cipher": { - "ignore_above": 1024, - "type": "keyword" - }, - "class": { - "ignore_above": 1024, - "type": "keyword" - }, - "cmd": { - "ignore_above": 1024, - "type": "keyword" - }, - "code": { - "ignore_above": 1024, - "type": "keyword" - }, - "compat": { - "ignore_above": 1024, - "type": "keyword" - }, - "daddr": { - "ignore_above": 1024, - "type": "keyword" - }, - "data": { - "ignore_above": 1024, - "type": "keyword" - }, - "default-context": { - "ignore_above": 1024, - "type": "keyword" - }, - "device": { - "ignore_above": 1024, - "type": "keyword" - }, - "dir": { - "ignore_above": 1024, - "type": "keyword" - }, - "direction": { - "ignore_above": 1024, - "type": "keyword" - }, - "dmac": { - "ignore_above": 1024, - "type": "keyword" - }, - "dport": { - "ignore_above": 1024, - "type": "keyword" - }, - "enforcing": { - "ignore_above": 1024, - "type": "keyword" - }, - "entries": { - "ignore_above": 1024, - "type": "keyword" - }, - "exit": { - "ignore_above": 1024, - "type": "keyword" - }, - "fam": { - "ignore_above": 1024, - "type": "keyword" - }, - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "fd": { - "ignore_above": 1024, - "type": "keyword" - }, - "fe": { - "ignore_above": 1024, - "type": "keyword" - }, - "feature": { - "ignore_above": 1024, - "type": "keyword" - }, - "fi": { - "ignore_above": 1024, - "type": "keyword" - }, - "file": { - "ignore_above": 1024, - "type": "keyword" - }, - "flags": { - "ignore_above": 1024, - "type": "keyword" - }, - "format": { - "ignore_above": 1024, - "type": "keyword" - }, - "fp": { - "ignore_above": 1024, - "type": "keyword" - }, - "fver": { - "ignore_above": 1024, - "type": "keyword" - }, - "grantors": { - "ignore_above": 1024, - "type": "keyword" - }, - "grp": { - "ignore_above": 1024, - "type": "keyword" - }, - "hook": { - "ignore_above": 1024, - "type": "keyword" - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "icmp_type": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "igid": { - "ignore_above": 1024, - "type": "keyword" - }, - "img-ctx": { - "ignore_above": 1024, - "type": "keyword" - }, - "inif": { - "ignore_above": 1024, - "type": "keyword" - }, - "ino": { - "ignore_above": 1024, - "type": "keyword" - }, - "inode_gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "inode_uid": { - "ignore_above": 1024, - "type": "keyword" - }, - "invalid_context": { - "ignore_above": 1024, - "type": "keyword" - }, - "ioctlcmd": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "ignore_above": 1024, - "type": "keyword" - }, - "ipid": { - "ignore_above": 1024, - "type": "keyword" - }, - "ipx-net": { - "ignore_above": 1024, - "type": "keyword" - }, - "items": { - "ignore_above": 1024, - "type": "keyword" - }, - "iuid": { - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "kind": { - "ignore_above": 1024, - "type": "keyword" - }, - "ksize": { - "ignore_above": 1024, - "type": "keyword" - }, - "laddr": { - "ignore_above": 1024, - "type": "keyword" - }, - "len": { - "ignore_above": 1024, - "type": "keyword" - }, - "list": { - "ignore_above": 1024, - "type": "keyword" - }, - "lport": { - "ignore_above": 1024, - "type": "keyword" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "macproto": { - "ignore_above": 1024, - "type": "keyword" - }, - "maj": { - "ignore_above": 1024, - "type": "keyword" - }, - "major": { - "ignore_above": 1024, - "type": "keyword" - }, - "minor": { - "ignore_above": 1024, - "type": "keyword" - }, - "model": { - "ignore_above": 1024, - "type": "keyword" - }, - "msg": { - "ignore_above": 1024, - "type": "keyword" - }, - "nargs": { - "ignore_above": 1024, - "type": "keyword" - }, - "net": { - "ignore_above": 1024, - "type": "keyword" - }, - "new": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-chardev": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-disk": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-enabled": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-fs": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-level": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-log_passwd": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-mem": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-net": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-range": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-rng": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-role": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-seuser": { - "ignore_above": 1024, - "type": "keyword" - }, - "new-vcpu": { - "ignore_above": 1024, - "type": "keyword" - }, - "new_gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "new_lock": { - "ignore_above": 1024, - "type": "keyword" - }, - "new_pe": { - "ignore_above": 1024, - "type": "keyword" - }, - "new_pi": { - "ignore_above": 1024, - "type": "keyword" - }, - "new_pp": { - "ignore_above": 1024, - "type": "keyword" - }, - "nlnk-fam": { - "ignore_above": 1024, - "type": "keyword" - }, - "nlnk-grp": { - "ignore_above": 1024, - "type": "keyword" - }, - "nlnk-pid": { - "ignore_above": 1024, - "type": "keyword" - }, - "oauid": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_uid": { - "ignore_above": 1024, - "type": "keyword" - }, - "ocomm": { - "ignore_above": 1024, - "type": "keyword" - }, - "oflag": { - "ignore_above": 1024, - "type": "keyword" - }, - "old": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-auid": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-chardev": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-disk": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-enabled": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-fs": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-level": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-log_passwd": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-mem": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-net": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-range": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-rng": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-role": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-ses": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-seuser": { - "ignore_above": 1024, - "type": "keyword" - }, - "old-vcpu": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_enforcing": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_lock": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_pe": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_pi": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_pp": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_prom": { - "ignore_above": 1024, - "type": "keyword" - }, - "old_val": { - "ignore_above": 1024, - "type": "keyword" - }, - "op": { - "ignore_above": 1024, - "type": "keyword" - }, - "opid": { - "ignore_above": 1024, - "type": "keyword" - }, - "oses": { - "ignore_above": 1024, - "type": "keyword" - }, - "outif": { - "ignore_above": 1024, - "type": "keyword" - }, - "parent": { - "ignore_above": 1024, - "type": "keyword" - }, - "per": { - "ignore_above": 1024, - "type": "keyword" - }, - "perm": { - "ignore_above": 1024, - "type": "keyword" - }, - "perm_mask": { - "ignore_above": 1024, - "type": "keyword" - }, - "permissive": { - "ignore_above": 1024, - "type": "keyword" - }, - "pfs": { - "ignore_above": 1024, - "type": "keyword" - }, - "printer": { - "ignore_above": 1024, - "type": "keyword" - }, - "prom": { - "ignore_above": 1024, - "type": "keyword" - }, - "proto": { - "ignore_above": 1024, - "type": "keyword" - }, - "qbytes": { - "ignore_above": 1024, - "type": "keyword" - }, - "range": { - "ignore_above": 1024, - "type": "keyword" - }, - "reason": { - "ignore_above": 1024, - "type": "keyword" - }, - "removed": { - "ignore_above": 1024, - "type": "keyword" - }, - "res": { - "ignore_above": 1024, - "type": "keyword" - }, - "resrc": { - "ignore_above": 1024, - "type": "keyword" - }, - "rport": { - "ignore_above": 1024, - "type": "keyword" - }, - "sauid": { - "ignore_above": 1024, - "type": "keyword" - }, - "scontext": { - "ignore_above": 1024, - "type": "keyword" - }, - "selected-context": { - "ignore_above": 1024, - "type": "keyword" - }, - "seperm": { - "ignore_above": 1024, - "type": "keyword" - }, - "seperms": { - "ignore_above": 1024, - "type": "keyword" - }, - "seqno": { - "ignore_above": 1024, - "type": "keyword" - }, - "seresult": { - "ignore_above": 1024, - "type": "keyword" - }, - "ses": { - "ignore_above": 1024, - "type": "keyword" - }, - "seuser": { - "ignore_above": 1024, - "type": "keyword" - }, - "sig": { - "ignore_above": 1024, - "type": "keyword" - }, - "sigev_signo": { - "ignore_above": 1024, - "type": "keyword" - }, - "smac": { - "ignore_above": 1024, - "type": "keyword" - }, - "socket": { - "properties": { - "addr": { - "ignore_above": 1024, - "type": "keyword" - }, - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "port": { - "ignore_above": 1024, - "type": "keyword" - }, - "saddr": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "spid": { - "ignore_above": 1024, - "type": "keyword" - }, - "sport": { - "ignore_above": 1024, - "type": "keyword" - }, - "state": { - "ignore_above": 1024, - "type": "keyword" - }, - "subj": { - "ignore_above": 1024, - "type": "keyword" - }, - "success": { - "ignore_above": 1024, - "type": "keyword" - }, - "syscall": { - "ignore_above": 1024, - "type": "keyword" - }, - "table": { - "ignore_above": 1024, - "type": "keyword" - }, - "tclass": { - "ignore_above": 1024, - "type": "keyword" - }, - "tcontext": { - "ignore_above": 1024, - "type": "keyword" - }, - "terminal": { - "ignore_above": 1024, - "type": "keyword" - }, - "tty": { - "ignore_above": 1024, - "type": "keyword" - }, - "unit": { - "ignore_above": 1024, - "type": "keyword" - }, - "uri": { - "ignore_above": 1024, - "type": "keyword" - }, - "uuid": { - "ignore_above": 1024, - "type": "keyword" - }, - "val": { - "ignore_above": 1024, - "type": "keyword" - }, - "ver": { - "ignore_above": 1024, - "type": "keyword" - }, - "virt": { - "ignore_above": 1024, - "type": "keyword" - }, - "vm": { - "ignore_above": 1024, - "type": "keyword" - }, - "vm-ctx": { - "ignore_above": 1024, - "type": "keyword" - }, - "vm-pid": { - "ignore_above": 1024, - "type": "keyword" - }, - "watch": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "message_type": { - "ignore_above": 1024, - "type": "keyword" - }, - "paths": { - "properties": { - "dev": { - "ignore_above": 1024, - "type": "keyword" - }, - "inode": { - "ignore_above": 1024, - "type": "keyword" - }, - "item": { - "ignore_above": 1024, - "type": "keyword" - }, - "mode": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "nametype": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_level": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_role": { - "ignore_above": 1024, - "type": "keyword" - }, - "obj_user": { - "ignore_above": 1024, - "type": "keyword" - }, - "objtype": { - "ignore_above": 1024, - "type": "keyword" - }, - "ogid": { - "ignore_above": 1024, - "type": "keyword" - }, - "ouid": { - "ignore_above": 1024, - "type": "keyword" - }, - "rdev": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "result": { - "ignore_above": 1024, - "type": "keyword" - }, - "sequence": { - "type": "long" - }, - "session": { - "ignore_above": 1024, - "type": "keyword" - }, - "summary": { - "properties": { - "actor": { - "properties": { - "primary": { - "ignore_above": 1024, - "type": "keyword" - }, - "secondary": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "how": { - "ignore_above": 1024, - "type": "keyword" - }, - "object": { - "properties": { - "primary": { - "ignore_above": 1024, - "type": "keyword" - }, - "secondary": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "client": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "account": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "availability_zone": { - "ignore_above": 1024, - "type": "keyword" - }, - "image": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "instance": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "machine": { - "properties": { - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "project": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "provider": { - "ignore_above": 1024, - "type": "keyword" - }, - "region": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "container": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "image": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "tag": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "labels": { - "type": "object" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "runtime": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "destination": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "dns": { - "properties": { - "answers": { - "properties": { - "class": { - "ignore_above": 1024, - "type": "keyword" - }, - "data": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "ttl": { - "type": "long" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "header_flags": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "op_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "question": { - "properties": { - "class": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "subdomain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "resolved_ip": { - "type": "ip" - }, - "response_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "docker": { - "properties": { - "container": { - "properties": { - "labels": { - "type": "object" - } - } - } - } - }, - "ecs": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "error": { - "properties": { - "code": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "message": { - "norms": false, - "type": "text" - }, - "stack_trace": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "event": { - "properties": { - "action": { - "ignore_above": 1024, - "type": "keyword" - }, - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "code": { - "ignore_above": 1024, - "type": "keyword" - }, - "created": { - "type": "date" - }, - "dataset": { - "ignore_above": 1024, - "type": "keyword" - }, - "duration": { - "type": "long" - }, - "end": { - "type": "date" - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "ingested": { - "type": "date" - }, - "kind": { - "ignore_above": 1024, - "type": "keyword" - }, - "module": { - "ignore_above": 1024, - "type": "keyword" - }, - "origin": { - "ignore_above": 1024, - "type": "keyword" - }, - "original": { - "ignore_above": 1024, - "type": "keyword" - }, - "outcome": { - "ignore_above": 1024, - "type": "keyword" - }, - "provider": { - "ignore_above": 1024, - "type": "keyword" - }, - "risk_score": { - "type": "float" - }, - "risk_score_norm": { - "type": "float" - }, - "sequence": { - "type": "long" - }, - "severity": { - "type": "long" - }, - "start": { - "type": "date" - }, - "timezone": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "fields": { - "type": "object" - }, - "file": { - "properties": { - "accessed": { - "type": "date" - }, - "attributes": { - "ignore_above": 1024, - "type": "keyword" - }, - "created": { - "type": "date" - }, - "ctime": { - "type": "date" - }, - "device": { - "ignore_above": 1024, - "type": "keyword" - }, - "directory": { - "ignore_above": 1024, - "type": "keyword" - }, - "drive_letter": { - "ignore_above": 1, - "type": "keyword" - }, - "extension": { - "ignore_above": 1024, - "type": "keyword" - }, - "gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "ignore_above": 1024, - "type": "keyword" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "inode": { - "ignore_above": 1024, - "type": "keyword" - }, - "mode": { - "ignore_above": 1024, - "type": "keyword" - }, - "mtime": { - "type": "date" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "origin": { - "fields": { - "raw": { - "ignore_above": 1024, - "type": "keyword" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "owner": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "selinux": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "level": { - "ignore_above": 1024, - "type": "keyword" - }, - "role": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "setgid": { - "type": "boolean" - }, - "setuid": { - "type": "boolean" - }, - "size": { - "type": "long" - }, - "target_path": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "uid": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "geoip": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "properties": { - "blake2b_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "blake2b_384": { - "ignore_above": 1024, - "type": "keyword" - }, - "blake2b_512": { - "ignore_above": 1024, - "type": "keyword" - }, - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha384": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_384": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_512": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512_224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "xxh64": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, - "containerized": { - "type": "boolean" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "ignore_above": 1024, - "type": "keyword" - }, - "codename": { - "ignore_above": 1024, - "type": "keyword" - }, - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "uptime": { - "type": "long" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "http": { - "properties": { - "request": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "bytes": { - "type": "long" - }, - "method": { - "ignore_above": 1024, - "type": "keyword" - }, - "referrer": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "response": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "bytes": { - "type": "long" - }, - "status_code": { - "type": "long" - } - } - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "jolokia": { - "properties": { - "agent": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "secured": { - "type": "boolean" - }, - "server": { - "properties": { - "product": { - "ignore_above": 1024, - "type": "keyword" - }, - "vendor": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "url": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "kubernetes": { - "properties": { - "annotations": { - "properties": { - "*": { - "type": "object" - } - } - }, - "container": { - "properties": { - "image": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "deployment": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "labels": { - "properties": { - "*": { - "type": "object" - } - } - }, - "namespace": { - "ignore_above": 1024, - "type": "keyword" - }, - "node": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "pod": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "uid": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "replicaset": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "statefulset": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "labels": { - "type": "object" - }, - "log": { - "properties": { - "level": { - "ignore_above": 1024, - "type": "keyword" - }, - "logger": { - "ignore_above": 1024, - "type": "keyword" - }, - "origin": { - "properties": { - "file": { - "properties": { - "line": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "function": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "original": { - "ignore_above": 1024, - "type": "keyword" - }, - "syslog": { - "properties": { - "facility": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "priority": { - "type": "long" - }, - "severity": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "message": { - "norms": false, - "type": "text" - }, - "network": { - "properties": { - "application": { - "ignore_above": 1024, - "type": "keyword" - }, - "bytes": { - "type": "long" - }, - "community_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "direction": { - "ignore_above": 1024, - "type": "keyword" - }, - "forwarded_ip": { - "type": "ip" - }, - "iana_number": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "packets": { - "type": "long" - }, - "protocol": { - "ignore_above": 1024, - "type": "keyword" - }, - "transport": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "observer": { - "properties": { - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "product": { - "ignore_above": 1024, - "type": "keyword" - }, - "serial_number": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "vendor": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "organization": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "os": { - "properties": { - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "package": { - "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, - "build_version": { - "ignore_above": 1024, - "type": "keyword" - }, - "checksum": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "install_scope": { - "ignore_above": 1024, - "type": "keyword" - }, - "installed": { - "type": "date" - }, - "license": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - }, - "size": { - "type": "long" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "process": { - "properties": { - "args": { - "ignore_above": 1024, - "type": "keyword" - }, - "args_count": { - "type": "long" - }, - "command_line": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "entity_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "executable": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "blake2b_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "blake2b_384": { - "ignore_above": 1024, - "type": "keyword" - }, - "blake2b_512": { - "ignore_above": 1024, - "type": "keyword" - }, - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha384": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_384": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha3_512": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512_224": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512_256": { - "ignore_above": 1024, - "type": "keyword" - }, - "xxh64": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "parent": { - "properties": { - "args": { - "ignore_above": 1024, - "type": "keyword" - }, - "args_count": { - "type": "long" - }, - "command_line": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "executable": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "exit_code": { - "type": "long" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "title": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "title": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "registry": { - "properties": { - "data": { - "properties": { - "bytes": { - "ignore_above": 1024, - "type": "keyword" - }, - "strings": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hive": { - "ignore_above": 1024, - "type": "keyword" - }, - "key": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "value": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "related": { - "properties": { - "ip": { - "type": "ip" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "rule": { - "properties": { - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - }, - "ruleset": { - "ignore_above": 1024, - "type": "keyword" - }, - "uuid": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "server": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "service": { - "properties": { - "ephemeral_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "node": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "state": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "socket": { - "properties": { - "entity_id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "source": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "system": { - "properties": { - "audit": { - "properties": { - "host": { - "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, - "boottime": { - "type": "date" - }, - "containerized": { - "type": "boolean" - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "codename": { - "ignore_above": 1024, - "type": "keyword" - }, - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "timezone": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "offset": { - "properties": { - "sec": { - "type": "long" - } - } - } - } - }, - "uptime": { - "type": "long" - } - } - }, - "package": { - "properties": { - "arch": { - "ignore_above": 1024, - "type": "keyword" - }, - "entity_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "installtime": { - "type": "date" - }, - "license": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "release": { - "ignore_above": 1024, - "type": "keyword" - }, - "size": { - "type": "long" - }, - "summary": { - "ignore_above": 1024, - "type": "keyword" - }, - "url": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "user": { - "properties": { - "dir": { - "ignore_above": 1024, - "type": "keyword" - }, - "gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "type": "object" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "password": { - "properties": { - "last_changed": { - "type": "date" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "shell": { - "ignore_above": 1024, - "type": "keyword" - }, - "uid": { - "ignore_above": 1024, - "type": "keyword" - }, - "user_information": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "tags": { - "ignore_above": 1024, - "type": "keyword" - }, - "threat": { - "properties": { - "framework": { - "ignore_above": 1024, - "type": "keyword" - }, - "tactic": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "technique": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "timeseries": { - "properties": { - "instance": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "tls": { - "properties": { - "cipher": { - "ignore_above": 1024, - "type": "keyword" - }, - "client": { - "properties": { - "certificate": { - "ignore_above": 1024, - "type": "keyword" - }, - "certificate_chain": { - "ignore_above": 1024, - "type": "keyword" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "issuer": { - "ignore_above": 1024, - "type": "keyword" - }, - "ja3": { - "ignore_above": 1024, - "type": "keyword" - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "server_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "subject": { - "ignore_above": 1024, - "type": "keyword" - }, - "supported_ciphers": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "curve": { - "ignore_above": 1024, - "type": "keyword" - }, - "established": { - "type": "boolean" - }, - "next_protocol": { - "ignore_above": 1024, - "type": "keyword" - }, - "resumed": { - "type": "boolean" - }, - "server": { - "properties": { - "certificate": { - "ignore_above": 1024, - "type": "keyword" - }, - "certificate_chain": { - "ignore_above": 1024, - "type": "keyword" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "issuer": { - "ignore_above": 1024, - "type": "keyword" - }, - "ja3s": { - "ignore_above": 1024, - "type": "keyword" - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "subject": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - }, - "version_protocol": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "tracing": { - "properties": { - "trace": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "transaction": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "url": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "extension": { - "ignore_above": 1024, - "type": "keyword" - }, - "fragment": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "original": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "password": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "port": { - "type": "long" - }, - "query": { - "ignore_above": 1024, - "type": "keyword" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "scheme": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "username": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "user": { - "properties": { - "audit": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "effective": { - "properties": { - "group": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "entity_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "filesystem": { - "properties": { - "group": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "name_map": { - "type": "object" - }, - "saved": { - "properties": { - "group": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "selinux": { - "properties": { - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "level": { - "ignore_above": 1024, - "type": "keyword" - }, - "role": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "terminal": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "user_agent": { - "properties": { - "device": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "original": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "vulnerability": { - "properties": { - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "classification": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "enumeration": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - }, - "report_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "scanner": { - "properties": { - "vendor": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "score": { - "properties": { - "base": { - "type": "float" - }, - "environmental": { - "type": "float" - }, - "temporal": { - "type": "float" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "severity": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "settings": { - "index": { - "lifecycle": { - "name": "auditbeat", - "rollover_alias": "auditbeat-7.6.2" - }, - "mapping": { - "total_fields": { - "limit": "10000" - } - }, - "number_of_replicas": "1", - "number_of_shards": "1", - "query": { - "default_field": [ - "message", - "tags", - "agent.ephemeral_id", - "agent.id", - "agent.name", - "agent.type", - "agent.version", - "as.organization.name", - "client.address", - "client.as.organization.name", - "client.domain", - "client.geo.city_name", - "client.geo.continent_name", - "client.geo.country_iso_code", - "client.geo.country_name", - "client.geo.name", - "client.geo.region_iso_code", - "client.geo.region_name", - "client.mac", - "client.registered_domain", - "client.top_level_domain", - "client.user.domain", - "client.user.email", - "client.user.full_name", - "client.user.group.domain", - "client.user.group.id", - "client.user.group.name", - "client.user.hash", - "client.user.id", - "client.user.name", - "cloud.account.id", - "cloud.availability_zone", - "cloud.instance.id", - "cloud.instance.name", - "cloud.machine.type", - "cloud.provider", - "cloud.region", - "container.id", - "container.image.name", - "container.image.tag", - "container.name", - "container.runtime", - "destination.address", - "destination.as.organization.name", - "destination.domain", - "destination.geo.city_name", - "destination.geo.continent_name", - "destination.geo.country_iso_code", - "destination.geo.country_name", - "destination.geo.name", - "destination.geo.region_iso_code", - "destination.geo.region_name", - "destination.mac", - "destination.registered_domain", - "destination.top_level_domain", - "destination.user.domain", - "destination.user.email", - "destination.user.full_name", - "destination.user.group.domain", - "destination.user.group.id", - "destination.user.group.name", - "destination.user.hash", - "destination.user.id", - "destination.user.name", - "dns.answers.class", - "dns.answers.data", - "dns.answers.name", - "dns.answers.type", - "dns.header_flags", - "dns.id", - "dns.op_code", - "dns.question.class", - "dns.question.name", - "dns.question.registered_domain", - "dns.question.subdomain", - "dns.question.top_level_domain", - "dns.question.type", - "dns.response_code", - "dns.type", - "ecs.version", - "error.code", - "error.id", - "error.message", - "error.stack_trace", - "error.type", - "event.action", - "event.category", - "event.code", - "event.dataset", - "event.hash", - "event.id", - "event.kind", - "event.module", - "event.original", - "event.outcome", - "event.provider", - "event.timezone", - "event.type", - "file.device", - "file.directory", - "file.extension", - "file.gid", - "file.group", - "file.hash.md5", - "file.hash.sha1", - "file.hash.sha256", - "file.hash.sha512", - "file.inode", - "file.mode", - "file.name", - "file.owner", - "file.path", - "file.target_path", - "file.type", - "file.uid", - "geo.city_name", - "geo.continent_name", - "geo.country_iso_code", - "geo.country_name", - "geo.name", - "geo.region_iso_code", - "geo.region_name", - "group.domain", - "group.id", - "group.name", - "hash.md5", - "hash.sha1", - "hash.sha256", - "hash.sha512", - "host.architecture", - "host.geo.city_name", - "host.geo.continent_name", - "host.geo.country_iso_code", - "host.geo.country_name", - "host.geo.name", - "host.geo.region_iso_code", - "host.geo.region_name", - "host.hostname", - "host.id", - "host.mac", - "host.name", - "host.os.family", - "host.os.full", - "host.os.kernel", - "host.os.name", - "host.os.platform", - "host.os.version", - "host.type", - "host.user.domain", - "host.user.email", - "host.user.full_name", - "host.user.group.domain", - "host.user.group.id", - "host.user.group.name", - "host.user.hash", - "host.user.id", - "host.user.name", - "http.request.body.content", - "http.request.method", - "http.request.referrer", - "http.response.body.content", - "http.version", - "log.level", - "log.logger", - "log.origin.file.name", - "log.origin.function", - "log.original", - "log.syslog.facility.name", - "log.syslog.severity.name", - "network.application", - "network.community_id", - "network.direction", - "network.iana_number", - "network.name", - "network.protocol", - "network.transport", - "network.type", - "observer.geo.city_name", - "observer.geo.continent_name", - "observer.geo.country_iso_code", - "observer.geo.country_name", - "observer.geo.name", - "observer.geo.region_iso_code", - "observer.geo.region_name", - "observer.hostname", - "observer.mac", - "observer.name", - "observer.os.family", - "observer.os.full", - "observer.os.kernel", - "observer.os.name", - "observer.os.platform", - "observer.os.version", - "observer.product", - "observer.serial_number", - "observer.type", - "observer.vendor", - "observer.version", - "organization.id", - "organization.name", - "os.family", - "os.full", - "os.kernel", - "os.name", - "os.platform", - "os.version", - "package.architecture", - "package.checksum", - "package.description", - "package.install_scope", - "package.license", - "package.name", - "package.path", - "package.version", - "process.args", - "text", - "process.executable", - "process.hash.md5", - "process.hash.sha1", - "process.hash.sha256", - "process.hash.sha512", - "process.name", - "text", - "text", - "text", - "text", - "text", - "process.thread.name", - "process.title", - "process.working_directory", - "server.address", - "server.as.organization.name", - "server.domain", - "server.geo.city_name", - "server.geo.continent_name", - "server.geo.country_iso_code", - "server.geo.country_name", - "server.geo.name", - "server.geo.region_iso_code", - "server.geo.region_name", - "server.mac", - "server.registered_domain", - "server.top_level_domain", - "server.user.domain", - "server.user.email", - "server.user.full_name", - "server.user.group.domain", - "server.user.group.id", - "server.user.group.name", - "server.user.hash", - "server.user.id", - "server.user.name", - "service.ephemeral_id", - "service.id", - "service.name", - "service.node.name", - "service.state", - "service.type", - "service.version", - "source.address", - "source.as.organization.name", - "source.domain", - "source.geo.city_name", - "source.geo.continent_name", - "source.geo.country_iso_code", - "source.geo.country_name", - "source.geo.name", - "source.geo.region_iso_code", - "source.geo.region_name", - "source.mac", - "source.registered_domain", - "source.top_level_domain", - "source.user.domain", - "source.user.email", - "source.user.full_name", - "source.user.group.domain", - "source.user.group.id", - "source.user.group.name", - "source.user.hash", - "source.user.id", - "source.user.name", - "threat.framework", - "threat.tactic.id", - "threat.tactic.name", - "threat.tactic.reference", - "threat.technique.id", - "threat.technique.name", - "threat.technique.reference", - "tracing.trace.id", - "tracing.transaction.id", - "url.domain", - "url.extension", - "url.fragment", - "url.full", - "url.original", - "url.password", - "url.path", - "url.query", - "url.registered_domain", - "url.scheme", - "url.top_level_domain", - "url.username", - "user.domain", - "user.email", - "user.full_name", - "user.group.domain", - "user.group.id", - "user.group.name", - "user.hash", - "user.id", - "user.name", - "user_agent.device.name", - "user_agent.name", - "text", - "user_agent.original", - "user_agent.os.family", - "user_agent.os.full", - "user_agent.os.kernel", - "user_agent.os.name", - "user_agent.os.platform", - "user_agent.os.version", - "user_agent.version", - "text", - "agent.hostname", - "timeseries.instance", - "cloud.project.id", - "cloud.image.id", - "host.os.build", - "host.os.codename", - "kubernetes.pod.name", - "kubernetes.pod.uid", - "kubernetes.namespace", - "kubernetes.node.name", - "kubernetes.replicaset.name", - "kubernetes.deployment.name", - "kubernetes.statefulset.name", - "kubernetes.container.name", - "kubernetes.container.image", - "jolokia.agent.version", - "jolokia.agent.id", - "jolokia.server.product", - "jolokia.server.version", - "jolokia.server.vendor", - "jolokia.url", - "raw", - "file.origin", - "file.selinux.user", - "file.selinux.role", - "file.selinux.domain", - "file.selinux.level", - "user.audit.id", - "user.audit.name", - "user.effective.id", - "user.effective.name", - "user.effective.group.id", - "user.effective.group.name", - "user.filesystem.id", - "user.filesystem.name", - "user.filesystem.group.id", - "user.filesystem.group.name", - "user.saved.id", - "user.saved.name", - "user.saved.group.id", - "user.saved.group.name", - "user.selinux.user", - "user.selinux.role", - "user.selinux.domain", - "user.selinux.level", - "user.selinux.category", - "source.path", - "destination.path", - "auditd.message_type", - "auditd.session", - "auditd.result", - "auditd.summary.actor.primary", - "auditd.summary.actor.secondary", - "auditd.summary.object.type", - "auditd.summary.object.primary", - "auditd.summary.object.secondary", - "auditd.summary.how", - "auditd.paths.inode", - "auditd.paths.dev", - "auditd.paths.obj_user", - "auditd.paths.obj_role", - "auditd.paths.obj_domain", - "auditd.paths.obj_level", - "auditd.paths.objtype", - "auditd.paths.ouid", - "auditd.paths.rdev", - "auditd.paths.nametype", - "auditd.paths.ogid", - "auditd.paths.item", - "auditd.paths.mode", - "auditd.paths.name", - "auditd.data.action", - "auditd.data.minor", - "auditd.data.acct", - "auditd.data.addr", - "auditd.data.cipher", - "auditd.data.id", - "auditd.data.entries", - "auditd.data.kind", - "auditd.data.ksize", - "auditd.data.spid", - "auditd.data.arch", - "auditd.data.argc", - "auditd.data.major", - "auditd.data.unit", - "auditd.data.table", - "auditd.data.terminal", - "auditd.data.grantors", - "auditd.data.direction", - "auditd.data.op", - "auditd.data.tty", - "auditd.data.syscall", - "auditd.data.data", - "auditd.data.family", - "auditd.data.mac", - "auditd.data.pfs", - "auditd.data.items", - "auditd.data.a0", - "auditd.data.a1", - "auditd.data.a2", - "auditd.data.a3", - "auditd.data.hostname", - "auditd.data.lport", - "auditd.data.rport", - "auditd.data.exit", - "auditd.data.fp", - "auditd.data.laddr", - "auditd.data.sport", - "auditd.data.capability", - "auditd.data.nargs", - "auditd.data.new-enabled", - "auditd.data.audit_backlog_limit", - "auditd.data.dir", - "auditd.data.cap_pe", - "auditd.data.model", - "auditd.data.new_pp", - "auditd.data.old-enabled", - "auditd.data.oauid", - "auditd.data.old", - "auditd.data.banners", - "auditd.data.feature", - "auditd.data.vm-ctx", - "auditd.data.opid", - "auditd.data.seperms", - "auditd.data.seresult", - "auditd.data.new-rng", - "auditd.data.old-net", - "auditd.data.sigev_signo", - "auditd.data.ino", - "auditd.data.old_enforcing", - "auditd.data.old-vcpu", - "auditd.data.range", - "auditd.data.res", - "auditd.data.added", - "auditd.data.fam", - "auditd.data.nlnk-pid", - "auditd.data.subj", - "auditd.data.a[0-3]", - "auditd.data.cgroup", - "auditd.data.kernel", - "auditd.data.ocomm", - "auditd.data.new-net", - "auditd.data.permissive", - "auditd.data.class", - "auditd.data.compat", - "auditd.data.fi", - "auditd.data.changed", - "auditd.data.msg", - "auditd.data.dport", - "auditd.data.new-seuser", - "auditd.data.invalid_context", - "auditd.data.dmac", - "auditd.data.ipx-net", - "auditd.data.iuid", - "auditd.data.macproto", - "auditd.data.obj", - "auditd.data.ipid", - "auditd.data.new-fs", - "auditd.data.vm-pid", - "auditd.data.cap_pi", - "auditd.data.old-auid", - "auditd.data.oses", - "auditd.data.fd", - "auditd.data.igid", - "auditd.data.new-disk", - "auditd.data.parent", - "auditd.data.len", - "auditd.data.oflag", - "auditd.data.uuid", - "auditd.data.code", - "auditd.data.nlnk-grp", - "auditd.data.cap_fp", - "auditd.data.new-mem", - "auditd.data.seperm", - "auditd.data.enforcing", - "auditd.data.new-chardev", - "auditd.data.old-rng", - "auditd.data.outif", - "auditd.data.cmd", - "auditd.data.hook", - "auditd.data.new-level", - "auditd.data.sauid", - "auditd.data.sig", - "auditd.data.audit_backlog_wait_time", - "auditd.data.printer", - "auditd.data.old-mem", - "auditd.data.perm", - "auditd.data.old_pi", - "auditd.data.state", - "auditd.data.format", - "auditd.data.new_gid", - "auditd.data.tcontext", - "auditd.data.maj", - "auditd.data.watch", - "auditd.data.device", - "auditd.data.grp", - "auditd.data.bool", - "auditd.data.icmp_type", - "auditd.data.new_lock", - "auditd.data.old_prom", - "auditd.data.acl", - "auditd.data.ip", - "auditd.data.new_pi", - "auditd.data.default-context", - "auditd.data.inode_gid", - "auditd.data.new-log_passwd", - "auditd.data.new_pe", - "auditd.data.selected-context", - "auditd.data.cap_fver", - "auditd.data.file", - "auditd.data.net", - "auditd.data.virt", - "auditd.data.cap_pp", - "auditd.data.old-range", - "auditd.data.resrc", - "auditd.data.new-range", - "auditd.data.obj_gid", - "auditd.data.proto", - "auditd.data.old-disk", - "auditd.data.audit_failure", - "auditd.data.inif", - "auditd.data.vm", - "auditd.data.flags", - "auditd.data.nlnk-fam", - "auditd.data.old-fs", - "auditd.data.old-ses", - "auditd.data.seqno", - "auditd.data.fver", - "auditd.data.qbytes", - "auditd.data.seuser", - "auditd.data.cap_fe", - "auditd.data.new-vcpu", - "auditd.data.old-level", - "auditd.data.old_pp", - "auditd.data.daddr", - "auditd.data.old-role", - "auditd.data.ioctlcmd", - "auditd.data.smac", - "auditd.data.apparmor", - "auditd.data.fe", - "auditd.data.perm_mask", - "auditd.data.ses", - "auditd.data.cap_fi", - "auditd.data.obj_uid", - "auditd.data.reason", - "auditd.data.list", - "auditd.data.old_lock", - "auditd.data.bus", - "auditd.data.old_pe", - "auditd.data.new-role", - "auditd.data.prom", - "auditd.data.uri", - "auditd.data.audit_enabled", - "auditd.data.old-log_passwd", - "auditd.data.old-seuser", - "auditd.data.per", - "auditd.data.scontext", - "auditd.data.tclass", - "auditd.data.ver", - "auditd.data.new", - "auditd.data.val", - "auditd.data.img-ctx", - "auditd.data.old-chardev", - "auditd.data.old_val", - "auditd.data.success", - "auditd.data.inode_uid", - "auditd.data.removed", - "auditd.data.socket.port", - "auditd.data.socket.saddr", - "auditd.data.socket.addr", - "auditd.data.socket.family", - "auditd.data.socket.path", - "geoip.continent_name", - "geoip.city_name", - "geoip.region_name", - "geoip.country_iso_code", - "hash.blake2b_256", - "hash.blake2b_384", - "hash.blake2b_512", - "hash.md5", - "hash.sha1", - "hash.sha224", - "hash.sha256", - "hash.sha384", - "hash.sha3_224", - "hash.sha3_256", - "hash.sha3_384", - "hash.sha3_512", - "hash.sha512", - "hash.sha512_224", - "hash.sha512_256", - "hash.xxh64", - "event.origin", - "user.entity_id", - "user.terminal", - "process.entity_id", - "process.hash.blake2b_256", - "process.hash.blake2b_384", - "process.hash.blake2b_512", - "process.hash.sha224", - "process.hash.sha384", - "process.hash.sha3_224", - "process.hash.sha3_256", - "process.hash.sha3_384", - "process.hash.sha3_512", - "process.hash.sha512_224", - "process.hash.sha512_256", - "process.hash.xxh64", - "socket.entity_id", - "system.audit.host.timezone.name", - "system.audit.host.hostname", - "system.audit.host.id", - "system.audit.host.architecture", - "system.audit.host.mac", - "system.audit.host.os.codename", - "system.audit.host.os.platform", - "system.audit.host.os.name", - "system.audit.host.os.family", - "system.audit.host.os.version", - "system.audit.host.os.kernel", - "system.audit.package.entity_id", - "system.audit.package.name", - "system.audit.package.version", - "system.audit.package.release", - "system.audit.package.arch", - "system.audit.package.license", - "system.audit.package.summary", - "system.audit.package.url", - "system.audit.user.name", - "system.audit.user.uid", - "system.audit.user.gid", - "system.audit.user.dir", - "system.audit.user.shell", - "system.audit.user.user_information", - "system.audit.user.password.type", - "fields.*" - ] - }, - "refresh_interval": "5s" - } - } - } -} From fd81692b98e69615e94f4e6dae89f19b0322d9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Mon, 28 Dec 2020 17:00:42 +0100 Subject: [PATCH 55/72] [Security Solution] Skip failing Cypress tests (#86967) --- .../cypress/integration/timeline_attach_to_case.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts index bbb6f672f1112..a0051eee0a22e 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts @@ -18,7 +18,8 @@ import { createTimeline } from '../tasks/api_calls/timelines'; import { cleanKibana } from '../tasks/common'; import { createCase } from '../tasks/api_calls/cases'; -describe('attach timeline to case', () => { +// https://github.com/elastic/kibana/issues/86959 +describe.skip('attach timeline to case', () => { const myTimeline = { ...timeline }; context('without cases created', () => { From 83f6440c03d062349caf7dfc42c71baf4fef151d Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 28 Dec 2020 11:10:36 -0600 Subject: [PATCH 56/72] skip "Closes and opens alerts" #83773 --- .../security_solution/cypress/integration/alerts.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts index 82e214398f69a..c409dbc7814fc 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts @@ -35,7 +35,7 @@ import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; -describe('Alerts', () => { +describe.skip('Alerts', () => { context('Closing alerts', () => { beforeEach(() => { cleanKibana(); From 3ddc527d500bbf7a49969085518c1d694702b4a0 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 28 Dec 2020 13:29:16 -0600 Subject: [PATCH 57/72] skip "Fields Browser rendering. #60209 --- .../cypress/integration/fields_browser.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts index 98cb7418a08a6..55ded8014db3c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts @@ -46,7 +46,7 @@ const defaultHeaders = [ ]; describe('Fields Browser', () => { - context('Fields Browser rendering', () => { + context.skip('Fields Browser rendering', () => { before(() => { cleanKibana(); loginAndWaitForPage(HOSTS_URL); From db2f7e6bbb2152e677c57ece722f6ffbd907ad22 Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 28 Dec 2020 13:45:45 -0700 Subject: [PATCH 58/72] [ftr/flags] improve help text (#86971) Co-authored-by: spalger --- .../src/functional_test_runner/cli.ts | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/kbn-test/src/functional_test_runner/cli.ts b/packages/kbn-test/src/functional_test_runner/cli.ts index 8f53d6f7cf58b..2dfc9ded66201 100644 --- a/packages/kbn-test/src/functional_test_runner/cli.ts +++ b/packages/kbn-test/src/functional_test_runner/cli.ts @@ -141,22 +141,27 @@ export function runFtrCli() { config: 'test/functional/config.js', }, help: ` - --config=path path to a config file - --bail stop tests after the first failure - --grep pattern used to select which tests to run - --invert invert grep to exclude tests - --include=file a test file to be included, pass multiple times for multiple files - --exclude=file a test file to be excluded, pass multiple times for multiple files - --include-tag=tag a tag to be included, pass multiple times for multiple tags - --exclude-tag=tag a tag to be excluded, pass multiple times for multiple tags - --test-stats print the number of tests (included and excluded) to STDERR - --updateBaselines replace baseline screenshots with whatever is generated from the test - --updateSnapshots replace inline and file snapshots with whatever is generated from the test - -u replace both baseline screenshots and snapshots - --kibana-install-dir directory where the Kibana install being tested resides - --throttle enable network throttling in Chrome browser - --headless run browser in headless mode - `, + --config=path path to a config file + --bail stop tests after the first failure + --grep pattern used to select which tests to run + --invert invert grep to exclude tests + --include=file a test file to be included, pass multiple times for multiple files + --exclude=file a test file to be excluded, pass multiple times for multiple files + --include-tag=tag a tag to be included, pass multiple times for multiple tags. Only + suites which have one of the passed include-tag tags will be executed. + When combined with the --exclude-tag flag both conditions must be met + for a suite to run. + --exclude-tag=tag a tag to be excluded, pass multiple times for multiple tags. Any suite + which has any of the exclude-tags will be excluded. When combined with + the --include-tag flag both conditions must be met for a suite to run. + --test-stats print the number of tests (included and excluded) to STDERR + --updateBaselines replace baseline screenshots with whatever is generated from the test + --updateSnapshots replace inline and file snapshots with whatever is generated from the test + -u replace both baseline screenshots and snapshots + --kibana-install-dir directory where the Kibana install being tested resides + --throttle enable network throttling in Chrome browser + --headless run browser in headless mode + `, }, } ); From 4290b58b6a9e44dd8d38228d88dd0555d20b6840 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 28 Dec 2020 14:49:05 -0600 Subject: [PATCH 59/72] skip "adds correctly a filter to the global search bar" #86552 --- .../security_solution/cypress/integration/search_bar.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts index 7fcbc10f88b44..e5e74f6eb0cac 100644 --- a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts @@ -13,7 +13,7 @@ import { HOSTS_URL } from '../urls/navigation'; import { waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts'; import { cleanKibana } from '../tasks/common'; -describe('SearchBar', () => { +describe.skip('SearchBar', () => { before(() => { cleanKibana(); loginAndWaitForPage(HOSTS_URL); From 54390f02f98b3cda6890c05b74c3f78d6e6dd822 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 28 Dec 2020 14:55:14 -0600 Subject: [PATCH 60/72] skip network and timeline inspection. #85677, #85678 --- .../security_solution/cypress/integration/inspect.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts index 6321be1e26151..98891e65771ce 100644 --- a/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts @@ -18,7 +18,7 @@ import { executeTimelineKQL, openTimelineInspectButton } from '../tasks/timeline import { HOSTS_URL, NETWORK_URL } from '../urls/navigation'; -describe('Inspect', () => { +describe.skip('Inspect', () => { context('Hosts stats and tables', () => { before(() => { cleanKibana(); From 92b2b60ad5ddbe8bfbfb0676749e2a7176860adc Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 28 Dec 2020 15:11:01 -0600 Subject: [PATCH 61/72] [logging/json] use merge from kbn/std (#86330) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/logging/layouts/json_layout.ts | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/core/server/logging/layouts/json_layout.ts b/src/core/server/logging/layouts/json_layout.ts index 7573d0b837416..34c3c325e7328 100644 --- a/src/core/server/logging/layouts/json_layout.ts +++ b/src/core/server/logging/layouts/json_layout.ts @@ -18,7 +18,7 @@ */ import moment from 'moment-timezone'; -import { merge } from 'lodash'; +import { merge } from '@kbn/std'; import { schema } from '@kbn/config-schema'; import { LogRecord, Layout } from '@kbn/logging'; @@ -53,22 +53,19 @@ export class JsonLayout implements Layout { } public format(record: LogRecord): string { - return JSON.stringify( - merge( - { - '@timestamp': moment(record.timestamp).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), - message: record.message, - error: JsonLayout.errorToSerializableObject(record.error), - log: { - level: record.level.id.toUpperCase(), - logger: record.context, - }, - process: { - pid: record.pid, - }, - }, - record.meta - ) - ); + const log = { + '@timestamp': moment(record.timestamp).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), + message: record.message, + error: JsonLayout.errorToSerializableObject(record.error), + log: { + level: record.level.id.toUpperCase(), + logger: record.context, + }, + process: { + pid: record.pid, + }, + }; + const output = record.meta ? merge(log, record.meta) : log; + return JSON.stringify(output); } } From 62fd430fdf6d32841a72ff088fb38af0dbe2fb90 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 28 Dec 2020 15:24:15 -0600 Subject: [PATCH 62/72] skip "Custom detection rules" #83772 --- .../cypress/integration/alerts_detection_rules_custom.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index d0b0862034a3b..897f035d23b10 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -212,7 +212,7 @@ describe('Custom detection rules creation', () => { }); }); -describe('Custom detection rules deletion and edition', () => { +describe.skip('Custom detection rules deletion and edition', () => { context('Deletion', () => { beforeEach(() => { cleanKibana(); From d843450620d1016e47142db3ac657c95e3d5edfb Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 28 Dec 2020 17:34:19 -0600 Subject: [PATCH 63/72] skip "pagination updates results and page number" #86975 --- .../security_solution/cypress/integration/pagination.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts b/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts index 2896b2dbc36c6..95cbf8220402f 100644 --- a/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts @@ -17,7 +17,7 @@ import { refreshPage } from '../tasks/security_header'; import { HOSTS_PAGE_TAB_URLS } from '../urls/navigation'; -describe('Pagination', () => { +describe.skip('Pagination', () => { before(() => { cleanKibana(); loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); From bd908c6ba3786f562ba8568919c15f19f3818250 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Tue, 29 Dec 2020 14:18:30 +0100 Subject: [PATCH 64/72] [Lens] Integrate searchSessionId into Lens app (#86297) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../lens/public/app_plugin/app.test.tsx | 133 +++++++++++++++++- x-pack/plugins/lens/public/app_plugin/app.tsx | 68 +++++---- .../lens/public/app_plugin/mounter.tsx | 1 + .../plugins/lens/public/app_plugin/types.ts | 6 +- .../editor_frame/editor_frame.test.tsx | 2 + .../editor_frame/editor_frame.tsx | 3 +- .../editor_frame/state_management.test.ts | 1 + .../editor_frame/suggestion_panel.tsx | 8 +- .../workspace_panel/workspace_panel.tsx | 4 +- .../public/editor_frame_service/mocks.tsx | 1 + .../editor_frame_service/service.test.tsx | 2 + .../public/editor_frame_service/service.tsx | 2 + x-pack/plugins/lens/public/types.ts | 2 + 13 files changed, 189 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 1496b0c335322..5e38cb49114e9 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -74,6 +74,16 @@ function createMockFrame(): jest.Mocked { }; } +function createMockSearchService() { + let sessionIdCounter = 1; + return { + session: { + start: jest.fn(() => `sessionId-${sessionIdCounter++}`), + clear: jest.fn(), + }, + }; +} + function createMockFilterManager() { const unsubscribe = jest.fn(); @@ -118,16 +128,29 @@ function createMockQueryString() { function createMockTimefilter() { const unsubscribe = jest.fn(); + let timeFilter = { from: 'now-7d', to: 'now' }; + let subscriber: () => void; return { - getTime: jest.fn(() => ({ from: 'now-7d', to: 'now' })), - setTime: jest.fn(), + getTime: jest.fn(() => timeFilter), + setTime: jest.fn((newTimeFilter) => { + timeFilter = newTimeFilter; + if (subscriber) { + subscriber(); + } + }), getTimeUpdate$: () => ({ subscribe: ({ next }: { next: () => void }) => { + subscriber = next; return unsubscribe; }, }), getRefreshInterval: () => {}, getRefreshIntervalDefaults: () => {}, + getAutoRefreshFetch$: () => ({ + subscribe: ({ next }: { next: () => void }) => { + return next; + }, + }), }; } @@ -209,6 +232,7 @@ describe('Lens App', () => { return new Promise((resolve) => resolve({ id })); }), }, + search: createMockSearchService(), } as unknown) as DataPublicPluginStart, storage: { get: jest.fn(), @@ -295,6 +319,7 @@ describe('Lens App', () => { "query": "", }, "savedQuery": undefined, + "searchSessionId": "sessionId-1", "showNoDataPopover": [Function], }, ], @@ -1072,6 +1097,53 @@ describe('Lens App', () => { }) ); }); + + it('updates the searchSessionId when the user changes query or time in the search bar', () => { + const { component, frame, services } = mountWith({}); + act(() => + component.find(TopNavMenu).prop('onQuerySubmit')!({ + dateRange: { from: 'now-14d', to: 'now-7d' }, + query: { query: '', language: 'lucene' }, + }) + ); + component.update(); + expect(frame.mount).toHaveBeenCalledWith( + expect.any(Element), + expect.objectContaining({ + searchSessionId: `sessionId-1`, + }) + ); + + // trigger again, this time changing just the query + act(() => + component.find(TopNavMenu).prop('onQuerySubmit')!({ + dateRange: { from: 'now-14d', to: 'now-7d' }, + query: { query: 'new', language: 'lucene' }, + }) + ); + component.update(); + expect(frame.mount).toHaveBeenCalledWith( + expect.any(Element), + expect.objectContaining({ + searchSessionId: `sessionId-2`, + }) + ); + + const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; + const field = ({ name: 'myfield' } as unknown) as IFieldType; + act(() => + services.data.query.filterManager.setFilters([ + esFilters.buildExistsFilter(field, indexPattern), + ]) + ); + component.update(); + expect(frame.mount).toHaveBeenCalledWith( + expect.any(Element), + expect.objectContaining({ + searchSessionId: `sessionId-3`, + }) + ); + }); }); describe('saved query handling', () => { @@ -1165,6 +1237,37 @@ describe('Lens App', () => { ); }); + it('updates the searchSessionId when the query is updated', () => { + const { component, frame } = mountWith({}); + act(() => { + component.find(TopNavMenu).prop('onSaved')!({ + id: '1', + attributes: { + title: '', + description: '', + query: { query: '', language: 'lucene' }, + }, + }); + }); + act(() => { + component.find(TopNavMenu).prop('onSavedQueryUpdated')!({ + id: '2', + attributes: { + title: 'new title', + description: '', + query: { query: '', language: 'lucene' }, + }, + }); + }); + component.update(); + expect(frame.mount).toHaveBeenCalledWith( + expect.any(Element), + expect.objectContaining({ + searchSessionId: `sessionId-1`, + }) + ); + }); + it('clears all existing unpinned filters when the active saved query is cleared', () => { const { component, frame, services } = mountWith({}); act(() => @@ -1190,6 +1293,32 @@ describe('Lens App', () => { }) ); }); + + it('updates the searchSessionId when the active saved query is cleared', () => { + const { component, frame, services } = mountWith({}); + act(() => + component.find(TopNavMenu).prop('onQuerySubmit')!({ + dateRange: { from: 'now-14d', to: 'now-7d' }, + query: { query: 'new', language: 'lucene' }, + }) + ); + const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; + const field = ({ name: 'myfield' } as unknown) as IFieldType; + const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; + const unpinned = esFilters.buildExistsFilter(field, indexPattern); + const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); + FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); + act(() => services.data.query.filterManager.setFilters([pinned, unpinned])); + component.update(); + act(() => component.find(TopNavMenu).prop('onClearSavedQuery')!()); + component.update(); + expect(frame.mount).toHaveBeenCalledWith( + expect.any(Element), + expect.objectContaining({ + searchSessionId: `sessionId-2`, + }) + ); + }); }); describe('showing a confirm message when leaving', () => { diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index bb77c5998519d..3f10cb341105c 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -7,7 +7,7 @@ import './app.scss'; import _ from 'lodash'; -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; import { EuiBreadcrumb } from '@elastic/eui'; @@ -71,7 +71,6 @@ export function App({ } = useKibana().services; const [state, setState] = useState(() => { - const currentRange = data.query.timefilter.timefilter.getTime(); return { query: data.query.queryString.getQuery(), // Do not use app-specific filters from previous app, @@ -81,14 +80,11 @@ export function App({ : data.query.filterManager.getFilters(), isLoading: Boolean(initialInput), indexPatternsForTopNav: [], - dateRange: { - fromDate: currentRange.from, - toDate: currentRange.to, - }, isLinkedToOriginatingApp: Boolean(incomingState?.originatingApp), isSaveModalVisible: false, indicateNoData: false, isSaveable: false, + searchSessionId: data.search.session.start(), }; }); @@ -107,10 +103,14 @@ export function App({ state.indicateNoData, state.query, state.filters, - state.dateRange, state.indexPatternsForTopNav, + state.searchSessionId, ]); + // Need a stable reference for the frame component of the dateRange + const { from: fromDate, to: toDate } = data.query.timefilter.timefilter.getTime(); + const currentDateRange = useMemo(() => ({ fromDate, toDate }), [fromDate, toDate]); + const onError = useCallback( (e: { message: string }) => notifications.toasts.addDanger({ @@ -160,24 +160,35 @@ export function App({ const filterSubscription = data.query.filterManager.getUpdates$().subscribe({ next: () => { - setState((s) => ({ ...s, filters: data.query.filterManager.getFilters() })); + setState((s) => ({ + ...s, + filters: data.query.filterManager.getFilters(), + searchSessionId: data.search.session.start(), + })); trackUiEvent('app_filters_updated'); }, }); const timeSubscription = data.query.timefilter.timefilter.getTimeUpdate$().subscribe({ next: () => { - const currentRange = data.query.timefilter.timefilter.getTime(); setState((s) => ({ ...s, - dateRange: { - fromDate: currentRange.from, - toDate: currentRange.to, - }, + searchSessionId: data.search.session.start(), })); }, }); + const autoRefreshSubscription = data.query.timefilter.timefilter + .getAutoRefreshFetch$() + .subscribe({ + next: () => { + setState((s) => ({ + ...s, + searchSessionId: data.search.session.start(), + })); + }, + }); + const kbnUrlStateStorage = createKbnUrlStateStorage({ history, useHash: uiSettings.get('state:storeInSessionStorage'), @@ -192,10 +203,12 @@ export function App({ stopSyncingQueryServiceStateWithUrl(); filterSubscription.unsubscribe(); timeSubscription.unsubscribe(); + autoRefreshSubscription.unsubscribe(); }; }, [ data.query.filterManager, data.query.timefilter.timefilter, + data.search.session, notifications.toasts, uiSettings, data.query, @@ -594,21 +607,21 @@ export function App({ appName={'lens'} onQuerySubmit={(payload) => { const { dateRange, query } = payload; - if ( - dateRange.from !== state.dateRange.fromDate || - dateRange.to !== state.dateRange.toDate - ) { + const currentRange = data.query.timefilter.timefilter.getTime(); + if (dateRange.from !== currentRange.from || dateRange.to !== currentRange.to) { data.query.timefilter.timefilter.setTime(dateRange); trackUiEvent('app_date_change'); } else { + // Query has changed, renew the session id. + // Time change will be picked up by the time subscription + setState((s) => ({ + ...s, + searchSessionId: data.search.session.start(), + })); trackUiEvent('app_query_change'); } setState((s) => ({ ...s, - dateRange: { - fromDate: dateRange.from, - toDate: dateRange.to, - }, query: query || s.query, })); }} @@ -622,12 +635,6 @@ export function App({ setState((s) => ({ ...s, savedQuery: { ...savedQuery }, // Shallow query for reference issues - dateRange: savedQuery.attributes.timefilter - ? { - fromDate: savedQuery.attributes.timefilter.from, - toDate: savedQuery.attributes.timefilter.to, - } - : s.dateRange, })); }} onClearSavedQuery={() => { @@ -640,8 +647,8 @@ export function App({ })); }} query={state.query} - dateRangeFrom={state.dateRange.fromDate} - dateRangeTo={state.dateRange.toDate} + dateRangeFrom={fromDate} + dateRangeTo={toDate} indicateNoData={state.indicateNoData} />

    @@ -650,7 +657,8 @@ export function App({ className="lnsApp__frame" render={editorFrame.mount} nativeProps={{ - dateRange: state.dateRange, + searchSessionId: state.searchSessionId, + dateRange: currentDateRange, query: state.query, filters: state.filters, savedQuery: state.savedQuery, diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index fbfd9c5758948..e769e402ff0e1 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -216,6 +216,7 @@ export async function mountApp( params.element ); return () => { + data.search.session.clear(); instance.unmount(); unmountComponentAtNode(params.element); unlistenParentHistory(); diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index 869ccf52fb0bd..af0feabe68cf7 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -55,16 +55,12 @@ export interface LensAppState { // Determines whether the lens editor shows the 'save and return' button, and the originating app breadcrumb. isLinkedToOriginatingApp?: boolean; - // Properties needed to interface with TopNav - dateRange: { - fromDate: string; - toDate: string; - }; query: Query; filters: Filter[]; savedQuery?: SavedQuery; isSaveable: boolean; activeData?: TableInspectorAdapter; + searchSessionId: string; } export interface RedirectToOriginProps { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index b0879ac8cb886..ef95314c55581 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -60,6 +60,7 @@ function getDefaultProps() { }, palettes: chartPluginMock.createPaletteRegistry(), showNoDataPopover: jest.fn(), + searchSessionId: 'sessionId', }; } @@ -264,6 +265,7 @@ describe('editor_frame', () => { filters: [], dateRange: { fromDate: 'now-7d', toDate: 'now' }, availablePalettes: defaultProps.palettes, + searchSessionId: 'sessionId', }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 977947b5afbeb..d872920d815ad 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -43,6 +43,7 @@ export interface EditorFrameProps { query: Query; filters: Filter[]; savedQuery?: SavedQuery; + searchSessionId: string; onChange: (arg: { filterableIndexPatterns: string[]; doc: Document; @@ -105,7 +106,7 @@ export function EditorFrame(props: EditorFrameProps) { dateRange: props.dateRange, query: props.query, filters: props.filters, - + searchSessionId: props.searchSessionId, availablePalettes: props.palettes, addNewLayer() { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts index 792fdc6d1ace7..52328bc3a1440 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts @@ -39,6 +39,7 @@ describe('editor_frame state management', () => { query: { query: '', language: 'lucene' }, filters: [], showNoDataPopover: jest.fn(), + searchSessionId: 'sessionId', }; }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 338a998b6b4dc..e2c4fa959924a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -273,16 +273,18 @@ export function SuggestionPanel({ const contextRef = useRef(context); contextRef.current = context; + const sessionIdRef = useRef(frame.searchSessionId); + sessionIdRef.current = frame.searchSessionId; + const AutoRefreshExpressionRenderer = useMemo(() => { - const autoRefreshFetch$ = plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$(); return (props: ReactExpressionRendererProps) => ( ); - }, [plugins.data.query.timefilter.timefilter, ExpressionRendererComponent]); + }, [ExpressionRendererComponent]); const [lastSelectedSuggestion, setLastSelectedSuggestion] = useState(-1); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 99a5869a60872..eb16dabfd2f90 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -362,8 +362,6 @@ export const InnerVisualizationWrapper = ({ }; ExpressionRendererComponent: ReactExpressionRendererType; }) => { - const autoRefreshFetch$ = useMemo(() => timefilter.getAutoRefreshFetch$(), [timefilter]); - const context: ExecutionContextSearch = useMemo( () => ({ query: framePublicAPI.query, @@ -482,7 +480,7 @@ export const InnerVisualizationWrapper = ({ padding="m" expression={expression!} searchContext={context} - reload$={autoRefreshFetch$} + searchSessionId={framePublicAPI.searchSessionId} onEvent={onEvent} onData$={onData$} renderMode="edit" diff --git a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx index 5ab410a1c0af2..2152c18ffeda4 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx @@ -132,6 +132,7 @@ export function createMockFramePublicAPI(): FrameMock { get: () => palette, getAll: () => [palette], }, + searchSessionId: 'sessionId', }; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx index e9f8013ef7e2d..3687e0cce2f1d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx @@ -57,6 +57,7 @@ describe('editor_frame service', () => { indexPatternId: '1', fieldName: 'test', }, + searchSessionId: 'sessionId', }); instance.unmount(); })() @@ -78,6 +79,7 @@ describe('editor_frame service', () => { query: { query: '', language: 'lucene' }, filters: [], showNoDataPopover: jest.fn(), + searchSessionId: 'sessionId', }); instance.unmount(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 0562e9bf4d32e..d4e9522f3bed1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -138,6 +138,7 @@ export class EditorFrameService { onChange, showNoDataPopover, initialContext, + searchSessionId, } ) => { domElement = element; @@ -172,6 +173,7 @@ export class EditorFrameService { onChange={onChange} showNoDataPopover={showNoDataPopover} initialContext={initialContext} + searchSessionId={searchSessionId} /> , domElement diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 57308f9c18b40..a5e17a05cf71d 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -46,6 +46,7 @@ export interface EditorFrameProps { query: Query; filters: Filter[]; savedQuery?: SavedQuery; + searchSessionId: string; initialContext?: VisualizeFieldContext; // Frame loader (app or embeddable) is expected to call this when it loads and updates @@ -456,6 +457,7 @@ export interface FramePublicAPI { dateRange: DateRange; query: Query; filters: Filter[]; + searchSessionId: string; /** * A map of all available palettes (keys being the ids). From 2781bf3855b9d6aa1444f4a1896461352d78c06a Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Tue, 29 Dec 2020 14:18:54 +0100 Subject: [PATCH 65/72] [Lens] Add more chart editor tests based on the debug state (#86750) Co-authored-by: Joe Reuter Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../pie_visualization/render_function.tsx | 10 ++ .../__snapshots__/expression.test.tsx.snap | 7 ++ .../public/xy_visualization/expression.tsx | 10 ++ .../xy_visualization/xy_config_panel.tsx | 7 ++ .../test/functional/apps/lens/chart_data.ts | 107 ++++++++++++++++++ x-pack/test/functional/apps/lens/index.ts | 1 + .../test/functional/apps/lens/smokescreen.ts | 77 +++++++++++++ .../test/functional/page_objects/lens_page.ts | 13 +++ 8 files changed, 232 insertions(+) create mode 100644 x-pack/test/functional/apps/lens/chart_data.ts diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index 70a98e4cf8589..b4c81cfb6e9c3 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -38,6 +38,15 @@ import { } from '../../../../../src/plugins/charts/public'; import { LensIconChartDonut } from '../assets/chart_donut'; +declare global { + interface Window { + /** + * Flag used to enable debugState on elastic charts + */ + _echDebugStateFlag?: boolean; + } +} + const EMPTY_SLICE = Symbol('empty_slice'); export function PieComponent( @@ -251,6 +260,7 @@ export function PieComponent( >