From b8ca723c05a3c5d72a4fd3bb389f89ed88da3f60 Mon Sep 17 00:00:00 2001 From: Liam Thompson <32779855+leemthompo@users.noreply.github.com> Date: Thu, 27 Apr 2023 17:03:46 +0200 Subject: [PATCH 01/19] [Enterprise Search] Minor copyedit for Search Applications, Behavioral Analytics (#156012) --- .../analytics_collection_integrate_view.tsx | 4 ++-- .../components/engines/engines_list.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_view.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_view.tsx index 2b69286d43f77..3c0ac7ab25317 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_view.tsx @@ -75,7 +75,7 @@ const CORSStep = (): EuiContainedStepProps => ({ {`http.cors.allow-origin: "*" -http.cors.enabled: true +http.cors.enabled: true http.cors.allow-credentials: true http.cors.allow-methods: OPTIONS, POST http.cors.allow-headers: X-Requested-With, X-Auth-Token, Content-Type, Content-Length, Authorization, Access-Control-Allow-Headers, Accept`} @@ -272,7 +272,7 @@ export const AnalyticsCollectionIntegrateView: React.FC = ({ createEngineFlyoutOpen }) => description: ( Date: Thu, 27 Apr 2023 11:13:30 -0400 Subject: [PATCH 02/19] [Cases] Fixing a few more integration tests with arraysToEqual (#155942) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR fixes a few integration tests that were failing because the ordering of the bulk creation of attachments isn't guaranteed. The solution is to compare the results ignoring ordering within the arrays. Fixes: https://github.com/elastic/kibana/issues/154640 Flaky test runner results: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2178 🟢 --- .../tests/no_public_base_url/push.ts | 30 ++++++++----------- .../tests/trial/cases/push_case.ts | 29 ++++++++---------- 2 files changed, 25 insertions(+), 34 deletions(-) diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts index f0b07da160674..062a697ebb22e 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { RecordingServiceNowSimulator } from '@kbn/actions-simulators-plugin/server/servicenow_simulation'; +import { arraysToEqual } from '../../../common/lib/validation'; import { postCommentUserReq, postCommentAlertReq, @@ -32,8 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); - // Failing: See https://github.com/elastic/kibana/issues/154640 - describe.skip('push_case', () => { + describe('push_case', () => { describe('incident recorder server', () => { const actionsRemover = new ActionsRemover(supertest); let serviceNowSimulatorURL: string = ''; @@ -114,6 +114,14 @@ export default ({ getService }: FtrProviderContext): void => { Boolean(request.work_notes) ); + const allWorkNotes = allCommentRequests.map((request) => request.work_notes); + const expectedNotes = [ + 'This is a cool comment\n\nAdded by elastic.', + 'Isolated host host-name with comment: comment text\n\nAdded by elastic.', + 'Released host host-name with comment: comment text\n\nAdded by elastic.', + 'Elastic Alerts attached to the case: 3', + ]; + /** * For each of these comments a request is made: * postCommentUserReq, postCommentActionsReq, postCommentActionsReleaseReq, and a comment with the @@ -122,21 +130,9 @@ export default ({ getService }: FtrProviderContext): void => { */ expect(allCommentRequests.length).be(4); - // User comment: postCommentUserReq - expect(allCommentRequests[0].work_notes).eql('This is a cool comment\n\nAdded by elastic.'); - - // Isolate host comment: postCommentActionsReq - expect(allCommentRequests[1].work_notes).eql( - 'Isolated host host-name with comment: comment text\n\nAdded by elastic.' - ); - - // Unisolate host comment: postCommentActionsReleaseReq - expect(allCommentRequests[2].work_notes).eql( - 'Released host host-name with comment: comment text\n\nAdded by elastic.' - ); - - // Total alerts - expect(allCommentRequests[3].work_notes).eql('Elastic Alerts attached to the case: 3'); + // since we're using a bulk create we can't guarantee the ordering so we'll check that the values exist but not + // there specific order in the results + expect(arraysToEqual(allWorkNotes, expectedNotes)).to.be(true); }); }); }); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts index a9d4382fc08bc..608153386ffe5 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts @@ -60,6 +60,7 @@ import { secOnlyRead, superUser, } from '../../../../common/lib/authentication/users'; +import { arraysToEqual } from '../../../../common/lib/validation'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -217,6 +218,14 @@ export default ({ getService }: FtrProviderContext): void => { Boolean(request.work_notes) ); + const allWorkNotes = allCommentRequests.map((request) => request.work_notes); + const expectedNotes = [ + 'This is a cool comment\n\nAdded by elastic.', + 'Isolated host host-name with comment: comment text\n\nAdded by elastic.', + 'Released host host-name with comment: comment text\n\nAdded by elastic.', + `Elastic Alerts attached to the case: 3\n\nFor more details, view the alerts in Kibana\nAlerts URL: https://localhost:5601/app/management/insightsAndAlerting/cases/${patchedCase.id}/?tabId=alerts`, + ]; + /** * For each of these comments a request is made: * postCommentUserReq, postCommentActionsReq, postCommentActionsReleaseReq, and a comment with the @@ -225,23 +234,9 @@ export default ({ getService }: FtrProviderContext): void => { */ expect(allCommentRequests.length).be(4); - // User comment: postCommentUserReq - expect(allCommentRequests[0].work_notes).eql('This is a cool comment\n\nAdded by elastic.'); - - // Isolate host comment: postCommentActionsReq - expect(allCommentRequests[1].work_notes).eql( - 'Isolated host host-name with comment: comment text\n\nAdded by elastic.' - ); - - // Unisolate host comment: postCommentActionsReleaseReq - expect(allCommentRequests[2].work_notes).eql( - 'Released host host-name with comment: comment text\n\nAdded by elastic.' - ); - - // Total alerts - expect(allCommentRequests[3].work_notes).eql( - `Elastic Alerts attached to the case: 3\n\nFor more details, view the alerts in Kibana\nAlerts URL: https://localhost:5601/app/management/insightsAndAlerting/cases/${patchedCase.id}/?tabId=alerts` - ); + // since we're using a bulk create we can't guarantee the ordering so we'll check that the values exist but not + // there specific order in the results + expect(arraysToEqual(allWorkNotes, expectedNotes)).to.be(true); }); it('should format the totalAlerts with spaceId correctly', async () => { From b01d60a994511d28ceebc0ad5083ce47a1f53dc1 Mon Sep 17 00:00:00 2001 From: Adam Demjen Date: Thu, 27 Apr 2023 11:18:30 -0400 Subject: [PATCH 03/19] [Enterprise Search] ELSER model download component (#155334) ## Summary This PR adds a panel with 3 stages of ELSER trained model deployment: - Model is not deployed (with button to initiate deployment) - Model is deploying (polling status every 5 seconds) - Model has deployed Note that deploying the model does not start it; this will be implemented in a follow-up PR. The panel is discardable in the Pipelines screen but not discardable in the pipeline configuration flyout. --- .../common/ml_inference_pipeline/index.ts | 1 + .../create_text_expansion_model_api_logic.ts | 33 ++ .../fetch_text_expansion_model_api_logic.ts | 32 ++ .../text_expansion_callout.test.tsx | 121 ++++++++ .../ml_inference/text_expansion_callout.tsx | 268 +++++++++++----- .../text_expansion_callout_data.tsx | 55 ++++ .../text_expansion_callout_logic.test.ts | 291 ++++++++++++++++++ .../text_expansion_callout_logic.ts | 178 +++++++++++ .../ml_inference_pipeline_processors_card.tsx | 9 +- 9 files changed, 906 insertions(+), 82 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/create_text_expansion_model_api_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/fetch_text_expansion_model_api_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_data.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts diff --git a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts index a51f7f022d9f9..2aad66d425cc7 100644 --- a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts +++ b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts @@ -30,6 +30,7 @@ import { export const TEXT_EXPANSION_TYPE = SUPPORTED_PYTORCH_TASKS.TEXT_EXPANSION; export const TEXT_EXPANSION_FRIENDLY_TYPE = 'ELSER'; export const ML_INFERENCE_PREFIX = 'ml.inference.'; +export const ELSER_MODEL_ID = '.elser_model_1_SNAPSHOT'; export interface MlInferencePipelineParams { description?: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/create_text_expansion_model_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/create_text_expansion_model_api_logic.ts new file mode 100644 index 0000000000000..315815a02ef08 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/create_text_expansion_model_api_logic.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ELSER_MODEL_ID } from '../../../../../../common/ml_inference_pipeline'; +import { Actions, createApiLogic } from '../../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../../shared/http'; + +export type CreateTextExpansionModelArgs = undefined; + +export interface CreateTextExpansionModelResponse { + deploymentState: string; + modelId: string; +} + +export const createTextExpansionModel = async (): Promise => { + const route = `/internal/enterprise_search/ml/models/${ELSER_MODEL_ID}`; + return await HttpLogic.values.http.post(route, { + body: undefined, + }); +}; + +export const CreateTextExpansionModelApiLogic = createApiLogic( + ['create_text_expansion_model_api_logic'], + createTextExpansionModel +); + +export type CreateTextExpansionModelApiLogicActions = Actions< + CreateTextExpansionModelArgs, + CreateTextExpansionModelResponse +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/fetch_text_expansion_model_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/fetch_text_expansion_model_api_logic.ts new file mode 100644 index 0000000000000..dd9430dfb07cc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/fetch_text_expansion_model_api_logic.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ELSER_MODEL_ID } from '../../../../../../common/ml_inference_pipeline'; +import { Actions, createApiLogic } from '../../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../../shared/http'; + +export type FetchTextExpansionModelArgs = undefined; + +export interface FetchTextExpansionModelResponse { + deploymentState: string; + modelId: string; +} + +export const fetchTextExpansionModelStatus = async () => { + return await HttpLogic.values.http.get( + `/internal/enterprise_search/ml/models/${ELSER_MODEL_ID}` + ); +}; + +export const FetchTextExpansionModelApiLogic = createApiLogic( + ['fetch_text_expansion_model_api_logic'], + fetchTextExpansionModelStatus +); + +export type FetchTextExpansionModelApiLogicActions = Actions< + FetchTextExpansionModelArgs, + FetchTextExpansionModelResponse +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.test.tsx new file mode 100644 index 0000000000000..c0d9a714ea2f0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.test.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues } from '../../../../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiButton } from '@elastic/eui'; + +import { + TextExpansionCallOut, + DeployModel, + ModelDeploymentInProgress, + ModelDeployed, + TextExpansionDismissButton, +} from './text_expansion_callout'; + +jest.mock('./text_expansion_callout_data', () => ({ + useTextExpansionCallOutData: jest.fn(() => ({ + dismiss: jest.fn(), + isCreateButtonDisabled: false, + isDismissable: false, + show: true, + })), +})); + +const DEFAULT_VALUES = { + isCreateButtonDisabled: false, + isModelDownloadInProgress: false, + isModelDownloaded: false, +}; + +describe('TextExpansionCallOut', () => { + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(DEFAULT_VALUES); + }); + it('renders panel with deployment instructions if the model is not deployed', () => { + const wrapper = shallow(); + expect(wrapper.find(DeployModel).length).toBe(1); + }); + it('renders panel with deployment in progress status if the model is being deployed', () => { + setMockValues({ + ...DEFAULT_VALUES, + isModelDownloadInProgress: true, + }); + + const wrapper = shallow(); + expect(wrapper.find(ModelDeploymentInProgress).length).toBe(1); + }); + it('renders panel with deployment in progress status if the model has been deployed', () => { + setMockValues({ + ...DEFAULT_VALUES, + isModelDownloaded: true, + }); + + const wrapper = shallow(); + expect(wrapper.find(ModelDeployed).length).toBe(1); + }); + + describe('DeployModel', () => { + it('renders deploy button', () => { + const wrapper = shallow( + {}} isCreateButtonDisabled={false} isDismissable={false} /> + ); + expect(wrapper.find(EuiButton).length).toBe(1); + const button = wrapper.find(EuiButton); + expect(button.prop('disabled')).toBe(false); + }); + it('renders disabled deploy button if it is set to disabled', () => { + const wrapper = shallow( + {}} isCreateButtonDisabled isDismissable={false} /> + ); + expect(wrapper.find(EuiButton).length).toBe(1); + const button = wrapper.find(EuiButton); + expect(button.prop('disabled')).toBe(true); + }); + it('renders dismiss button if it is set to dismissable', () => { + const wrapper = shallow( + {}} isCreateButtonDisabled={false} isDismissable /> + ); + expect(wrapper.find(TextExpansionDismissButton).length).toBe(1); + }); + it('does not render dismiss button if it is set to non-dismissable', () => { + const wrapper = shallow( + {}} isCreateButtonDisabled={false} isDismissable={false} /> + ); + expect(wrapper.find(TextExpansionDismissButton).length).toBe(0); + }); + }); + + describe('ModelDeploymentInProgress', () => { + it('renders dismiss button if it is set to dismissable', () => { + const wrapper = shallow( {}} isDismissable />); + expect(wrapper.find(TextExpansionDismissButton).length).toBe(1); + }); + it('does not render dismiss button if it is set to non-dismissable', () => { + const wrapper = shallow( + {}} isDismissable={false} /> + ); + expect(wrapper.find(TextExpansionDismissButton).length).toBe(0); + }); + }); + + describe('ModelDeployed', () => { + it('renders dismiss button if it is set to dismissable', () => { + const wrapper = shallow( {}} isDismissable />); + expect(wrapper.find(TextExpansionDismissButton).length).toBe(1); + }); + it('does not render dismiss button if it is set to non-dismissable', () => { + const wrapper = shallow( {}} isDismissable={false} />); + expect(wrapper.find(TextExpansionDismissButton).length).toBe(0); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx index a6784afecabd0..53e3e9c492a59 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx @@ -5,15 +5,17 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React from 'react'; -import { useValues } from 'kea'; +import { useActions, useValues } from 'kea'; import { EuiBadge, + EuiButton, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, + EuiIcon, EuiLink, EuiPanel, EuiText, @@ -24,113 +26,217 @@ import { FormattedMessage, FormattedHTMLMessage } from '@kbn/i18n-react'; import { docLinks } from '../../../../../shared/doc_links'; -import { MLInferenceLogic } from './ml_inference_logic'; +import { useTextExpansionCallOutData } from './text_expansion_callout_data'; +import { TextExpansionCalloutLogic } from './text_expansion_callout_logic'; export interface TextExpansionCallOutState { dismiss: () => void; - dismissable: boolean; + isCreateButtonDisabled: boolean; + isDismissable: boolean; show: boolean; } export interface TextExpansionCallOutProps { - dismissable?: boolean; + isDismissable?: boolean; } -export const TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY = - 'enterprise-search-text-expansion-callout-dismissed'; - -export const useTextExpansionCallOutData = ({ - dismissable = false, -}: TextExpansionCallOutProps): TextExpansionCallOutState => { - const { supportedMLModels } = useValues(MLInferenceLogic); - - const doesNotHaveTextExpansionModel = useMemo(() => { - return !supportedMLModels.some((m) => m.inference_config?.text_expansion); - }, [supportedMLModels]); - - const [show, setShow] = useState(() => { - if (!dismissable) return true; - - try { - return localStorage.getItem(TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY) !== 'true'; - } catch { - return true; - } - }); - - useEffect(() => { - try { - localStorage.setItem(TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY, JSON.stringify(!show)); - } catch { - return; - } - }, [show]); - - const dismiss = useCallback(() => { - setShow(false); - }, []); - - return { dismiss, dismissable, show: doesNotHaveTextExpansionModel && show }; +export const TextExpansionDismissButton = ({ + dismiss, +}: Pick) => { + return ( + + ); }; -export const TextExpansionCallOut: React.FC = (props) => { - const { dismiss, dismissable, show } = useTextExpansionCallOutData(props); - - if (!show) return null; +export const DeployModel = ({ + dismiss, + isCreateButtonDisabled, + isDismissable, +}: Pick) => { + const { createTextExpansionModel } = useActions(TextExpansionCalloutLogic); return ( + + + + + + + + + +

+ + + +

+
+
+ {isDismissable && ( + + + + )} +
+
+ + + + + + + + + + + createTextExpansionModel(undefined)} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCallOut.deployButton.label', + { + defaultMessage: 'Deploy', + } + )} + + + + + + + + + + + +
+
+ ); +}; + +export const ModelDeploymentInProgress = ({ + dismiss, + isDismissable, +}: Pick) => ( + + + - - - +

- - - +

- {dismissable && ( + {isDismissable && ( - + )}
- - - - - - - +
+ + + + + +
+
+); + +export const ModelDeployed = ({ + dismiss, + isDismissable, +}: Pick) => ( + + + + + + + + + +

+ +

+
+
+ {isDismissable && ( + + + + )}
-
-
+ + + + + + + + +); + +export const TextExpansionCallOut: React.FC = (props) => { + const { dismiss, isDismissable, show } = useTextExpansionCallOutData(props); + const { isCreateButtonDisabled, isModelDownloadInProgress, isModelDownloaded } = + useValues(TextExpansionCalloutLogic); + + if (!show) return null; + + if (!!isModelDownloadInProgress) { + return ; + } else if (!!isModelDownloaded) { + return ; + } + + return ( + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_data.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_data.tsx new file mode 100644 index 0000000000000..35f8f105c1e1e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_data.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback, useEffect, useState } from 'react'; + +import { useValues } from 'kea'; + +import { TextExpansionCallOutProps, TextExpansionCallOutState } from './text_expansion_callout'; +import { TextExpansionCalloutLogic } from './text_expansion_callout_logic'; + +export const TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY = + 'enterprise-search-text-expansion-callout-dismissed'; + +const isDismissed = () => localStorage.getItem(TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY) === 'true'; + +export const useTextExpansionCallOutData = ({ + isDismissable = false, +}: TextExpansionCallOutProps): TextExpansionCallOutState => { + const { isCreateButtonDisabled } = useValues(TextExpansionCalloutLogic); + + const [show, setShow] = useState(() => { + if (!isDismissable) return true; + + try { + return !isDismissed(); + } catch { + return true; + } + }); + + useEffect(() => { + try { + if (!isDismissed()) { + localStorage.setItem(TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY, JSON.stringify(!show)); + } + } catch { + return; + } + }, [show]); + + const dismiss = useCallback(() => { + setShow(false); + }, []); + + return { + dismiss, + isCreateButtonDisabled, + isDismissable, + show, + }; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts new file mode 100644 index 0000000000000..23afb770bead4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts @@ -0,0 +1,291 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogicMounter } from '../../../../../__mocks__/kea_logic'; + +import { HttpResponse } from '@kbn/core/public'; + +import { ErrorResponse, Status } from '../../../../../../../common/types/api'; +import { MlModelDeploymentState } from '../../../../../../../common/types/ml'; +import { CreateTextExpansionModelApiLogic } from '../../../../api/ml_models/text_expansion/create_text_expansion_model_api_logic'; +import { FetchTextExpansionModelApiLogic } from '../../../../api/ml_models/text_expansion/fetch_text_expansion_model_api_logic'; + +import { + TextExpansionCalloutLogic, + TextExpansionCalloutValues, +} from './text_expansion_callout_logic'; + +const DEFAULT_VALUES: TextExpansionCalloutValues = { + createTextExpansionModelStatus: Status.IDLE, + createdTextExpansionModel: undefined, + isCreateButtonDisabled: false, + isModelDownloadInProgress: false, + isModelDownloaded: false, + isPollingTextExpansionModelActive: false, + textExpansionModel: undefined, + textExpansionModelPollTimeoutId: null, +}; + +jest.useFakeTimers(); + +describe('TextExpansionCalloutLogic', () => { + const { mount } = new LogicMounter(TextExpansionCalloutLogic); + const { mount: mountCreateTextExpansionModelApiLogic } = new LogicMounter( + CreateTextExpansionModelApiLogic + ); + const { mount: mountFetchTextExpansionModelApiLogic } = new LogicMounter( + FetchTextExpansionModelApiLogic + ); + + beforeEach(() => { + jest.clearAllMocks(); + mountCreateTextExpansionModelApiLogic(); + mountFetchTextExpansionModelApiLogic(); + mount(); + }); + + it('has expected default values', () => { + expect(TextExpansionCalloutLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('listeners', () => { + describe('createTextExpansionModelPollingTimeout', () => { + const duration = 5000; + it('sets polling timeout', () => { + jest.spyOn(global, 'setTimeout'); + jest.spyOn(TextExpansionCalloutLogic.actions, 'setTextExpansionModelPollingId'); + + TextExpansionCalloutLogic.actions.createTextExpansionModelPollingTimeout(duration); + + expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), duration); + expect(TextExpansionCalloutLogic.actions.setTextExpansionModelPollingId).toHaveBeenCalled(); + }); + it('clears polling timeout if it is set', () => { + mount({ + ...DEFAULT_VALUES, + textExpansionModelPollTimeoutId: 'timeout-id', + }); + + jest.spyOn(global, 'clearTimeout'); + + TextExpansionCalloutLogic.actions.createTextExpansionModelPollingTimeout(duration); + + expect(clearTimeout).toHaveBeenCalledWith('timeout-id'); + }); + }); + + describe('createTextExpansionModelSuccess', () => { + it('sets createdTextExpansionModel', () => { + jest.spyOn(TextExpansionCalloutLogic.actions, 'fetchTextExpansionModel'); + jest.spyOn(TextExpansionCalloutLogic.actions, 'startPollingTextExpansionModel'); + + TextExpansionCalloutLogic.actions.createTextExpansionModelSuccess({ + deploymentState: MlModelDeploymentState.Downloading, + modelId: 'mock-model-id', + }); + + expect(TextExpansionCalloutLogic.actions.fetchTextExpansionModel).toHaveBeenCalled(); + expect(TextExpansionCalloutLogic.actions.startPollingTextExpansionModel).toHaveBeenCalled(); + }); + }); + + describe('fetchTextExpansionModelSuccess', () => { + const data = { + deploymentState: MlModelDeploymentState.Downloading, + modelId: 'mock-model-id', + }; + + it('starts polling when the model is downloading and polling is not active', () => { + mount({ + ...DEFAULT_VALUES, + }); + jest.spyOn(TextExpansionCalloutLogic.actions, 'startPollingTextExpansionModel'); + + TextExpansionCalloutLogic.actions.fetchTextExpansionModelSuccess(data); + + expect(TextExpansionCalloutLogic.actions.startPollingTextExpansionModel).toHaveBeenCalled(); + }); + it('sets polling timeout when the model is downloading and polling is active', () => { + mount({ + ...DEFAULT_VALUES, + textExpansionModelPollTimeoutId: 'timeout-id', + }); + jest.spyOn(TextExpansionCalloutLogic.actions, 'createTextExpansionModelPollingTimeout'); + + TextExpansionCalloutLogic.actions.fetchTextExpansionModelSuccess(data); + + expect( + TextExpansionCalloutLogic.actions.createTextExpansionModelPollingTimeout + ).toHaveBeenCalled(); + }); + it('stops polling when the model is downloaded and polling is active', () => { + mount({ + ...DEFAULT_VALUES, + textExpansionModelPollTimeoutId: 'timeout-id', + }); + jest.spyOn(TextExpansionCalloutLogic.actions, 'stopPollingTextExpansionModel'); + + TextExpansionCalloutLogic.actions.fetchTextExpansionModelSuccess({ + deploymentState: MlModelDeploymentState.Downloaded, + modelId: 'mock-model-id', + }); + + expect(TextExpansionCalloutLogic.actions.stopPollingTextExpansionModel).toHaveBeenCalled(); + }); + }); + + describe('fetchTextExpansionModelError', () => { + it('stops polling if it is active', () => { + mount({ + ...DEFAULT_VALUES, + textExpansionModelPollTimeoutId: 'timeout-id', + }); + jest.spyOn(TextExpansionCalloutLogic.actions, 'createTextExpansionModelPollingTimeout'); + + TextExpansionCalloutLogic.actions.fetchTextExpansionModelError({ + body: { + error: '', + message: 'some error', + statusCode: 500, + }, + } as HttpResponse); + + expect( + TextExpansionCalloutLogic.actions.createTextExpansionModelPollingTimeout + ).toHaveBeenCalled(); + }); + }); + + describe('startPollingTextExpansionModel', () => { + it('sets polling timeout', () => { + jest.spyOn(TextExpansionCalloutLogic.actions, 'createTextExpansionModelPollingTimeout'); + + TextExpansionCalloutLogic.actions.startPollingTextExpansionModel(); + + expect( + TextExpansionCalloutLogic.actions.createTextExpansionModelPollingTimeout + ).toHaveBeenCalled(); + }); + it('clears polling timeout if it is set', () => { + mount({ + ...DEFAULT_VALUES, + textExpansionModelPollTimeoutId: 'timeout-id', + }); + + jest.spyOn(global, 'clearTimeout'); + + TextExpansionCalloutLogic.actions.startPollingTextExpansionModel(); + + expect(clearTimeout).toHaveBeenCalledWith('timeout-id'); + }); + }); + + describe('stopPollingTextExpansionModel', () => { + it('clears polling timeout and poll timeout ID if it is set', () => { + mount({ + ...DEFAULT_VALUES, + textExpansionModelPollTimeoutId: 'timeout-id', + }); + + jest.spyOn(global, 'clearTimeout'); + jest.spyOn(TextExpansionCalloutLogic.actions, 'clearTextExpansionModelPollingId'); + + TextExpansionCalloutLogic.actions.stopPollingTextExpansionModel(); + + expect(clearTimeout).toHaveBeenCalledWith('timeout-id'); + expect( + TextExpansionCalloutLogic.actions.clearTextExpansionModelPollingId + ).toHaveBeenCalled(); + }); + }); + }); + + describe('reducers', () => { + describe('textExpansionModelPollTimeoutId', () => { + it('gets cleared on clearTextExpansionModelPollingId', () => { + TextExpansionCalloutLogic.actions.clearTextExpansionModelPollingId(); + + expect(TextExpansionCalloutLogic.values.textExpansionModelPollTimeoutId).toBe(null); + }); + it('gets set on setTextExpansionModelPollingId', () => { + const timeout = setTimeout(() => {}, 500); + TextExpansionCalloutLogic.actions.setTextExpansionModelPollingId(timeout); + + expect(TextExpansionCalloutLogic.values.textExpansionModelPollTimeoutId).toEqual(timeout); + }); + }); + }); + + describe('selectors', () => { + describe('isCreateButtonDisabled', () => { + it('is set to false if the fetch model API is idle', () => { + CreateTextExpansionModelApiLogic.actions.apiReset(); + expect(TextExpansionCalloutLogic.values.isCreateButtonDisabled).toBe(false); + }); + it('is set to true if the fetch model API is not idle', () => { + CreateTextExpansionModelApiLogic.actions.apiSuccess({ + deploymentState: MlModelDeploymentState.Downloading, + modelId: 'mock-model-id', + }); + expect(TextExpansionCalloutLogic.values.isCreateButtonDisabled).toBe(true); + }); + }); + + describe('isModelDownloadInProgress', () => { + it('is set to true if the model is downloading', () => { + FetchTextExpansionModelApiLogic.actions.apiSuccess({ + deploymentState: MlModelDeploymentState.Downloading, + modelId: 'mock-model-id', + }); + expect(TextExpansionCalloutLogic.values.isModelDownloadInProgress).toBe(true); + }); + it('is set to false if the model is downloading', () => { + FetchTextExpansionModelApiLogic.actions.apiSuccess({ + deploymentState: MlModelDeploymentState.Started, + modelId: 'mock-model-id', + }); + expect(TextExpansionCalloutLogic.values.isModelDownloadInProgress).toBe(false); + }); + }); + + describe('isModelDownloaded', () => { + it('is set to true if the model is downloaded', () => { + FetchTextExpansionModelApiLogic.actions.apiSuccess({ + deploymentState: MlModelDeploymentState.Downloaded, + modelId: 'mock-model-id', + }); + expect(TextExpansionCalloutLogic.values.isModelDownloaded).toBe(true); + }); + it('is set to false if the model is not downloaded', () => { + FetchTextExpansionModelApiLogic.actions.apiSuccess({ + deploymentState: MlModelDeploymentState.NotDeployed, + modelId: 'mock-model-id', + }); + expect(TextExpansionCalloutLogic.values.isModelDownloaded).toBe(false); + }); + }); + + describe('isPollingTextExpansionModelActive', () => { + it('is set to false if polling is not active', () => { + mount({ + ...DEFAULT_VALUES, + textExpansionModelPollTimeoutId: null, + }); + + expect(TextExpansionCalloutLogic.values.isPollingTextExpansionModelActive).toBe(false); + }); + it('is set to true if polling is active', () => { + mount({ + ...DEFAULT_VALUES, + textExpansionModelPollTimeoutId: 'timeout-id', + }); + + expect(TextExpansionCalloutLogic.values.isPollingTextExpansionModelActive).toBe(true); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts new file mode 100644 index 0000000000000..140cfb265f6d8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts @@ -0,0 +1,178 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { Status } from '../../../../../../../common/types/api'; +import { MlModelDeploymentState } from '../../../../../../../common/types/ml'; +import { + CreateTextExpansionModelApiLogic, + CreateTextExpansionModelApiLogicActions, + CreateTextExpansionModelResponse, +} from '../../../../api/ml_models/text_expansion/create_text_expansion_model_api_logic'; +import { + FetchTextExpansionModelApiLogic, + FetchTextExpansionModelApiLogicActions, + FetchTextExpansionModelResponse, +} from '../../../../api/ml_models/text_expansion/fetch_text_expansion_model_api_logic'; + +const FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION = 5000; // 5 seconds +const FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION_ON_FAILURE = 30000; // 30 seconds + +interface TextExpansionCalloutActions { + clearTextExpansionModelPollingId: () => void; + createTextExpansionModel: CreateTextExpansionModelApiLogicActions['makeRequest']; + createTextExpansionModelPollingTimeout: (duration: number) => { duration: number }; + createTextExpansionModelSuccess: CreateTextExpansionModelApiLogicActions['apiSuccess']; + fetchTextExpansionModel: FetchTextExpansionModelApiLogicActions['makeRequest']; + fetchTextExpansionModelError: FetchTextExpansionModelApiLogicActions['apiError']; + fetchTextExpansionModelSuccess: FetchTextExpansionModelApiLogicActions['apiSuccess']; + setTextExpansionModelPollingId: (pollTimeoutId: ReturnType) => { + pollTimeoutId: ReturnType; + }; + startPollingTextExpansionModel: () => void; + stopPollingTextExpansionModel: () => void; + textExpansionModel: FetchTextExpansionModelApiLogicActions['apiSuccess']; +} + +export interface TextExpansionCalloutValues { + createTextExpansionModelStatus: Status; + createdTextExpansionModel: CreateTextExpansionModelResponse | undefined; + isCreateButtonDisabled: boolean; + isModelDownloadInProgress: boolean; + isModelDownloaded: boolean; + isPollingTextExpansionModelActive: boolean; + textExpansionModel: FetchTextExpansionModelResponse | undefined; + textExpansionModelPollTimeoutId: null | ReturnType; +} + +export const TextExpansionCalloutLogic = kea< + MakeLogicType +>({ + actions: { + clearTextExpansionModelPollingId: true, + createTextExpansionModelPollingTimeout: (duration) => ({ duration }), + setTextExpansionModelPollingId: (pollTimeoutId: ReturnType) => ({ + pollTimeoutId, + }), + startPollingTextExpansionModel: true, + stopPollingTextExpansionModel: true, + }, + connect: { + actions: [ + CreateTextExpansionModelApiLogic, + ['makeRequest as createTextExpansionModel', 'apiSuccess as createTextExpansionModelSuccess'], + FetchTextExpansionModelApiLogic, + [ + 'makeRequest as fetchTextExpansionModel', + 'apiSuccess as fetchTextExpansionModelSuccess', + 'apiError as fetchTextExpansionModelError', + ], + ], + values: [ + CreateTextExpansionModelApiLogic, + ['data as createdTextExpansionModel', 'status as createTextExpansionModelStatus'], // error as ... + FetchTextExpansionModelApiLogic, + ['data as textExpansionModel'], + ], + }, + events: ({ actions, values }) => ({ + afterMount: () => { + actions.fetchTextExpansionModel(undefined); + }, + beforeUnmount: () => { + if (values.textExpansionModelPollTimeoutId !== null) { + actions.stopPollingTextExpansionModel(); + } + }, + }), + listeners: ({ actions, values }) => ({ + createTextExpansionModelPollingTimeout: ({ duration }) => { + if (values.textExpansionModelPollTimeoutId !== null) { + clearTimeout(values.textExpansionModelPollTimeoutId); + } + const timeoutId = setTimeout(() => { + actions.fetchTextExpansionModel(undefined); + }, duration); + actions.setTextExpansionModelPollingId(timeoutId); + }, + createTextExpansionModelSuccess: () => { + actions.fetchTextExpansionModel(undefined); + actions.startPollingTextExpansionModel(); + }, + fetchTextExpansionModelError: () => { + if (values.isPollingTextExpansionModelActive) { + actions.createTextExpansionModelPollingTimeout( + FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION_ON_FAILURE + ); + } + }, + fetchTextExpansionModelSuccess: (data) => { + if (data?.deploymentState === MlModelDeploymentState.Downloading) { + if (!values.isPollingTextExpansionModelActive) { + actions.startPollingTextExpansionModel(); + } else { + actions.createTextExpansionModelPollingTimeout( + FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION + ); + } + } else if ( + data?.deploymentState === MlModelDeploymentState.Downloaded && + values.isPollingTextExpansionModelActive + ) { + actions.stopPollingTextExpansionModel(); + } + }, + startPollingTextExpansionModel: () => { + if (values.textExpansionModelPollTimeoutId !== null) { + clearTimeout(values.textExpansionModelPollTimeoutId); + } + actions.createTextExpansionModelPollingTimeout(FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION); + }, + stopPollingTextExpansionModel: () => { + if (values.textExpansionModelPollTimeoutId !== null) { + clearTimeout(values.textExpansionModelPollTimeoutId); + actions.clearTextExpansionModelPollingId(); + } + }, + }), + path: ['enterprise_search', 'content', 'text_expansion_callout_logic'], + reducers: { + textExpansionModelPollTimeoutId: [ + null, + { + clearTextExpansionModelPollingId: () => null, + setTextExpansionModelPollingId: (_, { pollTimeoutId }) => pollTimeoutId, + }, + ], + }, + selectors: ({ selectors }) => ({ + isCreateButtonDisabled: [ + () => [selectors.createTextExpansionModelStatus], + (status: Status) => status !== Status.IDLE, + ], + isModelDownloadInProgress: [ + () => [selectors.textExpansionModel], + (data: FetchTextExpansionModelResponse) => + data?.deploymentState === MlModelDeploymentState.Downloading, + ], + isModelDownloaded: [ + () => [selectors.textExpansionModel], + (data: FetchTextExpansionModelResponse) => + data?.deploymentState === MlModelDeploymentState.Downloaded || + // TODO: add button for starting model, then remove these states + data?.deploymentState === MlModelDeploymentState.Starting || + data?.deploymentState === MlModelDeploymentState.Started || + data?.deploymentState === MlModelDeploymentState.FullyAllocated, + ], + isPollingTextExpansionModelActive: [ + () => [selectors.textExpansionModelPollTimeoutId], + (pollingTimeoutId: TextExpansionCalloutValues['textExpansionModelPollTimeoutId']) => + pollingTimeoutId !== null, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx index 4a6a6235982fd..6d3ad46a985c0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx @@ -12,6 +12,8 @@ import { useActions, useValues } from 'kea'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { InferencePipeline } from '../../../../../../common/types/pipelines'; +import { KibanaLogic } from '../../../../shared/kibana/kibana_logic'; +import { LicensingLogic } from '../../../../shared/licensing'; import { IndexNameLogic } from '../index_name_logic'; import { InferencePipelineCard } from './inference_pipeline_card'; @@ -20,6 +22,8 @@ import { TextExpansionCallOut } from './ml_inference/text_expansion_callout'; import { PipelinesLogic } from './pipelines_logic'; export const MlInferencePipelineProcessorsCard: React.FC = () => { + const { capabilities, isCloud } = useValues(KibanaLogic); + const { hasPlatinumLicense } = useValues(LicensingLogic); const { indexName } = useValues(IndexNameLogic); const { mlInferencePipelineProcessors: inferencePipelines } = useValues(PipelinesLogic); const { fetchMlInferenceProcessors, openAddMlInferencePipelineModal } = @@ -28,9 +32,12 @@ export const MlInferencePipelineProcessorsCard: React.FC = () => { fetchMlInferenceProcessors({ indexName }); }, [indexName]); + const hasMLPermissions = capabilities?.ml?.canGetTrainedModels ?? false; + const isGated = !isCloud && !hasPlatinumLicense; + return ( - + {hasMLPermissions && !isGated && } openAddMlInferencePipelineModal()} /> From 0c8f38b86fa17218bf37c486746a668b9efcf2e7 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Thu, 27 Apr 2023 10:30:14 -0500 Subject: [PATCH 04/19] [content management / maps] Content management / Saved object schema abstraction (#155342) ## Summary Abstract schema definitions for using Saved Objects with the content management api. For most schema types, this will reduce creation to only the attributes specific to a saved object. For Option types (create options, update options, search options) the saved object api is more complex and its likely that most SO types will only need to use a portion of it. In these cases we recommend using the provided schema definitions as a pattern for creating simpler schemas. Follow up to - https://github.com/elastic/kibana/pull/154985 - expresses types in schema form --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../kbn-content-management-utils/index.ts | 3 +- .../src/schema.ts | 122 ++++++++++++++++++ .../{ => src}/types.ts | 0 .../tsconfig.json | 1 + .../content_management/v1/cm_services.ts | 89 +++---------- 5 files changed, 143 insertions(+), 72 deletions(-) create mode 100644 packages/kbn-content-management-utils/src/schema.ts rename packages/kbn-content-management-utils/{ => src}/types.ts (100%) diff --git a/packages/kbn-content-management-utils/index.ts b/packages/kbn-content-management-utils/index.ts index 12594660136d8..8e4751ba223ea 100644 --- a/packages/kbn-content-management-utils/index.ts +++ b/packages/kbn-content-management-utils/index.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export * from './types'; +export * from './src/types'; +export * from './src/schema'; diff --git a/packages/kbn-content-management-utils/src/schema.ts b/packages/kbn-content-management-utils/src/schema.ts new file mode 100644 index 0000000000000..2e95624c36a22 --- /dev/null +++ b/packages/kbn-content-management-utils/src/schema.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { schema, ObjectType } from '@kbn/config-schema'; + +export const apiError = schema.object({ + error: schema.string(), + message: schema.string(), + statusCode: schema.number(), + metadata: schema.object({}, { unknowns: 'allow' }), +}); + +export const referenceSchema = schema.object( + { + name: schema.maybe(schema.string()), + type: schema.string(), + id: schema.string(), + }, + { unknowns: 'forbid' } +); + +export const referencesSchema = schema.arrayOf(referenceSchema); + +export const savedObjectSchema = (attributesSchema: ObjectType) => + schema.object( + { + id: schema.string(), + type: schema.string(), + version: schema.maybe(schema.string()), + createdAt: schema.maybe(schema.string()), + updatedAt: schema.maybe(schema.string()), + error: schema.maybe(apiError), + attributes: attributesSchema, + references: referencesSchema, + namespaces: schema.maybe(schema.arrayOf(schema.string())), + originId: schema.maybe(schema.string()), + }, + { unknowns: 'allow' } + ); + +export const objectTypeToGetResultSchema = (soSchema: ObjectType) => + schema.object( + { + item: soSchema, + meta: schema.object( + { + outcome: schema.oneOf([ + schema.literal('exactMatch'), + schema.literal('aliasMatch'), + schema.literal('conflict'), + ]), + aliasTargetId: schema.maybe(schema.string()), + aliasPurpose: schema.maybe( + schema.oneOf([ + schema.literal('savedObjectConversion'), + schema.literal('savedObjectImport'), + ]) + ), + }, + { unknowns: 'forbid' } + ), + }, + { unknowns: 'forbid' } + ); + +// its recommended to create a subset of this schema for stricter validation +export const createOptionsSchemas = { + id: schema.maybe(schema.string()), + references: schema.maybe(referencesSchema), + overwrite: schema.maybe(schema.boolean()), + version: schema.maybe(schema.string()), + refresh: schema.maybe(schema.boolean()), + initialNamespaces: schema.maybe(schema.arrayOf(schema.string())), +}; + +export const schemaAndOr = schema.oneOf([schema.literal('AND'), schema.literal('OR')]); + +// its recommended to create a subset of this schema for stricter validation +export const searchOptionsSchemas = { + page: schema.maybe(schema.number()), + perPage: schema.maybe(schema.number()), + sortField: schema.maybe(schema.string()), + sortOrder: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])), + fields: schema.maybe(schema.arrayOf(schema.string())), + search: schema.maybe(schema.string()), + searchFields: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + rootSearchFields: schema.maybe(schema.arrayOf(schema.string())), + + hasReference: schema.maybe(schema.oneOf([referenceSchema, schema.arrayOf(referenceSchema)])), + hasReferenceOperator: schema.maybe(schemaAndOr), + hasNoReference: schema.maybe(schema.oneOf([referenceSchema, schema.arrayOf(referenceSchema)])), + hasNoReferenceOperator: schema.maybe(schemaAndOr), + defaultSearchOperator: schema.maybe(schemaAndOr), + namespaces: schema.maybe(schema.arrayOf(schema.string())), + type: schema.maybe(schema.string()), + + filter: schema.maybe(schema.string()), + pit: schema.maybe( + schema.object({ id: schema.string(), keepAlive: schema.maybe(schema.string()) }) + ), +}; + +// its recommended to create a subset of this schema for stricter validation +export const updateOptionsSchema = { + references: schema.maybe(referencesSchema), + version: schema.maybe(schema.string()), + refresh: schema.maybe(schema.oneOf([schema.boolean(), schema.literal('wait_for')])), + upsert: (attributesSchema: ObjectType) => schema.maybe(savedObjectSchema(attributesSchema)), + retryOnConflict: schema.maybe(schema.number()), +}; + +export const createResultSchema = (soSchema: ObjectType) => + schema.object( + { + item: soSchema, + }, + { unknowns: 'forbid' } + ); diff --git a/packages/kbn-content-management-utils/types.ts b/packages/kbn-content-management-utils/src/types.ts similarity index 100% rename from packages/kbn-content-management-utils/types.ts rename to packages/kbn-content-management-utils/src/types.ts diff --git a/packages/kbn-content-management-utils/tsconfig.json b/packages/kbn-content-management-utils/tsconfig.json index 7de04c3c13451..89a1d2520f322 100644 --- a/packages/kbn-content-management-utils/tsconfig.json +++ b/packages/kbn-content-management-utils/tsconfig.json @@ -17,6 +17,7 @@ ], "kbn_references": [ "@kbn/content-management-plugin", + "@kbn/config-schema", "@kbn/core-saved-objects-api-server", ] } diff --git a/x-pack/plugins/maps/common/content_management/v1/cm_services.ts b/x-pack/plugins/maps/common/content_management/v1/cm_services.ts index 5ea8008f53225..65d2e3082da7d 100644 --- a/x-pack/plugins/maps/common/content_management/v1/cm_services.ts +++ b/x-pack/plugins/maps/common/content_management/v1/cm_services.ts @@ -6,24 +6,12 @@ */ import { schema } from '@kbn/config-schema'; import type { ContentManagementServicesDefinition as ServicesDefinition } from '@kbn/object-versioning'; - -const apiError = schema.object({ - error: schema.string(), - message: schema.string(), - statusCode: schema.number(), - metadata: schema.object({}, { unknowns: 'allow' }), -}); - -const referenceSchema = schema.object( - { - name: schema.maybe(schema.string()), - type: schema.string(), - id: schema.string(), - }, - { unknowns: 'forbid' } -); - -const referencesSchema = schema.arrayOf(referenceSchema); +import { + savedObjectSchema, + objectTypeToGetResultSchema, + createOptionsSchemas, + createResultSchema, +} from '@kbn/content-management-utils'; const mapAttributesSchema = schema.object( { @@ -36,48 +24,19 @@ const mapAttributesSchema = schema.object( { unknowns: 'forbid' } ); -const mapSavedObjectSchema = schema.object( - { - id: schema.string(), - type: schema.string(), - version: schema.maybe(schema.string()), - createdAt: schema.maybe(schema.string()), - updatedAt: schema.maybe(schema.string()), - error: schema.maybe(apiError), - attributes: mapAttributesSchema, - references: referencesSchema, - namespaces: schema.maybe(schema.arrayOf(schema.string())), - originId: schema.maybe(schema.string()), - }, - { unknowns: 'allow' } -); +const mapSavedObjectSchema = savedObjectSchema(mapAttributesSchema); -const getResultSchema = schema.object( - { - item: mapSavedObjectSchema, - meta: schema.object( - { - outcome: schema.oneOf([ - schema.literal('exactMatch'), - schema.literal('aliasMatch'), - schema.literal('conflict'), - ]), - aliasTargetId: schema.maybe(schema.string()), - aliasPurpose: schema.maybe( - schema.oneOf([ - schema.literal('savedObjectConversion'), - schema.literal('savedObjectImport'), - ]) - ), - }, - { unknowns: 'forbid' } - ), - }, - { unknowns: 'forbid' } +const searchOptionsSchema = schema.maybe( + schema.object( + { + onlyTitle: schema.maybe(schema.boolean()), + }, + { unknowns: 'forbid' } + ) ); const createOptionsSchema = schema.object({ - references: schema.maybe(referencesSchema), + references: schema.maybe(createOptionsSchemas.references), }); // Content management service definition. @@ -86,7 +45,7 @@ export const serviceDefinition: ServicesDefinition = { get: { out: { result: { - schema: getResultSchema, + schema: objectTypeToGetResultSchema(mapSavedObjectSchema), }, }, }, @@ -101,12 +60,7 @@ export const serviceDefinition: ServicesDefinition = { }, out: { result: { - schema: schema.object( - { - item: mapSavedObjectSchema, - }, - { unknowns: 'forbid' } - ), + schema: createResultSchema(mapSavedObjectSchema), }, }, }, @@ -123,14 +77,7 @@ export const serviceDefinition: ServicesDefinition = { search: { in: { options: { - schema: schema.maybe( - schema.object( - { - onlyTitle: schema.maybe(schema.boolean()), - }, - { unknowns: 'forbid' } - ) - ), + schema: searchOptionsSchema, }, }, }, From 99ee941d7aaa2d0b9326763e7669eb6f58b219b6 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Thu, 27 Apr 2023 11:32:07 -0400 Subject: [PATCH 05/19] [Security Solution][Endpoint] Cleanup and improvements to `run_endpoint_agent.js` CLI tool (#155730) ## Summary As a follow up to PR #155455 : - `run_endpoint_agent` CLI: Removed internal code that handles the creation of a VM and the enrollment of the agent with fleet and replaced it with use of methods now found in `endpoint_host_services` - created new service for managing agent download cache on local system - enhanced `run_endpoint_agent` to use cached version of agent download, and thus increase the performance of this tool --- .../cypress/support/data_loaders.ts | 7 +- .../common/agent_downloads_service.ts | 161 ++++++++++++++++++ .../endpoint/common/endpoint_host_services.ts | 79 ++++++++- .../endpoint/common/settings_storage.ts | 18 +- .../endpoint_agent_runner/elastic_endpoint.ts | 131 ++------------ 5 files changed, 262 insertions(+), 134 deletions(-) create mode 100644 x-pack/plugins/security_solution/scripts/endpoint/common/agent_downloads_service.ts diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts index ffcca01a6f1e9..4a006fdacc353 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts @@ -195,7 +195,12 @@ export const dataLoadersForRealEndpoints = ( options: Omit ): Promise => { const { kbnClient, log } = await stackServicesPromise; - return createAndEnrollEndpointHost({ ...options, log, kbnClient }).then((newHost) => { + return createAndEnrollEndpointHost({ + useClosestVersionMatch: true, + ...options, + log, + kbnClient, + }).then((newHost) => { return waitForEndpointToStreamData(kbnClient, newHost.agentId, 120000).then(() => { return newHost; }); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/agent_downloads_service.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/agent_downloads_service.ts new file mode 100644 index 0000000000000..ed5d0296d61a4 --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/agent_downloads_service.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mkdir, readdir, stat, unlink } from 'fs/promises'; +import { join } from 'path'; +import fs from 'fs'; +import nodeFetch from 'node-fetch'; +import { finished } from 'stream/promises'; +import { SettingsStorage } from './settings_storage'; + +export interface DownloadedAgentInfo { + filename: string; + directory: string; + fullFilePath: string; +} + +interface AgentDownloadStorageSettings { + /** + * Last time a cleanup was ran. Date in ISO format + */ + lastCleanup: string; + + /** + * The max file age in milliseconds. Defaults to 2 days + */ + maxFileAge: number; +} + +/** + * Class for managing Agent Downloads on the local disk + * @private + */ +class AgentDownloadStorage extends SettingsStorage { + private downloadsFolderExists = false; + private readonly downloadsDirName = 'agent_download_storage'; + private readonly downloadsDirFullPath: string; + + constructor() { + super('agent_download_storage_settings.json', { + defaultSettings: { + maxFileAge: 1.728e8, // 2 days + lastCleanup: new Date().toISOString(), + }, + }); + + this.downloadsDirFullPath = this.buildPath(this.downloadsDirName); + } + + protected async ensureExists(): Promise { + await super.ensureExists(); + + if (!this.downloadsFolderExists) { + await mkdir(this.downloadsDirFullPath, { recursive: true }); + this.downloadsFolderExists = true; + } + } + + public getPathsForUrl(agentDownloadUrl: string): DownloadedAgentInfo { + const filename = agentDownloadUrl.replace(/^https?:\/\//gi, '').replace(/\//g, '#'); + const directory = this.downloadsDirFullPath; + const fullFilePath = this.buildPath(join(this.downloadsDirName, filename)); + + return { + filename, + directory, + fullFilePath, + }; + } + + public async downloadAndStore(agentDownloadUrl: string): Promise { + // TODO: should we add "retry" attempts to file downloads? + + await this.ensureExists(); + + const newDownloadInfo = this.getPathsForUrl(agentDownloadUrl); + + // If download is already present on disk, then just return that info. No need to re-download it + if (fs.existsSync(newDownloadInfo.fullFilePath)) { + return newDownloadInfo; + } + + try { + const outputStream = fs.createWriteStream(newDownloadInfo.fullFilePath); + const { body } = await nodeFetch(agentDownloadUrl); + + await finished(body.pipe(outputStream)); + } catch (e) { + // Try to clean up download case it failed halfway through + await unlink(newDownloadInfo.fullFilePath); + + throw e; + } + + return newDownloadInfo; + } + + public async cleanupDownloads(): Promise<{ deleted: string[] }> { + const settings = await this.get(); + const maxAgeDate = new Date(); + const response: { deleted: string[] } = { deleted: [] }; + + maxAgeDate.setMilliseconds(settings.maxFileAge * -1); // `* -1` to set time back + + // If cleanup already happen within the file age, then nothing to do. Exit. + if (settings.lastCleanup > maxAgeDate.toISOString()) { + return response; + } + + await this.save({ + ...settings, + lastCleanup: new Date().toISOString(), + }); + + const deleteFilePromises: Array> = []; + const allFiles = await readdir(this.downloadsDirFullPath); + + for (const fileName of allFiles) { + const filePath = join(this.downloadsDirFullPath, fileName); + const fileStats = await stat(filePath); + + if (fileStats.isFile() && fileStats.birthtime < maxAgeDate) { + deleteFilePromises.push(unlink(filePath)); + response.deleted.push(filePath); + } + } + + await Promise.allSettled(deleteFilePromises); + + return response; + } +} + +const agentDownloadsClient = new AgentDownloadStorage(); + +/** + * Downloads the agent file provided via the input URL to a local folder on disk. If the file + * already exists on disk, then no download is actually done - the information about the cached + * version is returned instead + * @param agentDownloadUrl + */ +export const downloadAndStoreAgent = async ( + agentDownloadUrl: string +): Promise => { + const downloadedAgent = await agentDownloadsClient.downloadAndStore(agentDownloadUrl); + + return { + url: agentDownloadUrl, + ...downloadedAgent, + }; +}; + +/** + * Cleans up the old agent downloads on disk. + */ +export const cleanupDownloads = async (): ReturnType => { + return agentDownloadsClient.cleanupDownloads(); +}; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts index 4bb03324f172e..5b249ee238436 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts @@ -10,6 +10,8 @@ import type { KbnClient } from '@kbn/test'; import type { ToolingLog } from '@kbn/tooling-log'; import execa from 'execa'; import assert from 'assert'; +import type { DownloadedAgentInfo } from './agent_downloads_service'; +import { cleanupDownloads, downloadAndStoreAgent } from './agent_downloads_service'; import { fetchAgentPolicyEnrollmentKey, fetchFleetServerUrl, @@ -28,6 +30,10 @@ export interface CreateAndEnrollEndpointHostOptions version?: string; /** The name for the host. Will also be the name of the VM */ hostname?: string; + /** If `version` should be exact, or if this is `true`, then the closest version will be used. Defaults to `false` */ + useClosestVersionMatch?: boolean; + /** If the local cache of agent downloads should be used. Defaults to `true` */ + useCache?: boolean; } export interface CreateAndEnrollEndpointHostResponse { @@ -47,8 +53,14 @@ export const createAndEnrollEndpointHost = async ({ memory, hostname, version = kibanaPackageJson.version, + useClosestVersionMatch = false, + useCache = true, }: CreateAndEnrollEndpointHostOptions): Promise => { - const [vm, agentDownloadUrl, fleetServerUrl, enrollmentToken] = await Promise.all([ + let cacheCleanupPromise: ReturnType = Promise.resolve({ + deleted: [], + }); + + const [vm, agentDownload, fleetServerUrl, enrollmentToken] = await Promise.all([ createMultipassVm({ vmName: hostname ?? `test-host-${Math.random().toString().substring(2, 6)}`, disk, @@ -56,15 +68,33 @@ export const createAndEnrollEndpointHost = async ({ memory, }), - getAgentDownloadUrl(version, true, log), + getAgentDownloadUrl(version, useClosestVersionMatch, log).then<{ + url: string; + cache?: DownloadedAgentInfo; + }>((url) => { + if (useCache) { + cacheCleanupPromise = cleanupDownloads(); + + return downloadAndStoreAgent(url).then((cache) => { + return { + url, + cache, + }; + }); + } + + return { url }; + }), fetchFleetServerUrl(kbnClient), fetchAgentPolicyEnrollmentKey(kbnClient, agentPolicyId), ]); + log.verbose(await execa('multipass', ['info', vm.vmName])); + // Some validations before we proceed - assert(agentDownloadUrl, 'Missing agent download URL'); + assert(agentDownload.url, 'Missing agent download URL'); assert(fleetServerUrl, 'Fleet server URL not set'); assert(enrollmentToken, `No enrollment token for agent policy id [${agentPolicyId}]`); @@ -76,11 +106,22 @@ export const createAndEnrollEndpointHost = async ({ kbnClient, log, fleetServerUrl, - agentDownloadUrl, + agentDownloadUrl: agentDownload.url, + cachedAgentDownload: agentDownload.cache, enrollmentToken, vmName: vm.vmName, }); + await cacheCleanupPromise.then((results) => { + if (results.deleted.length > 0) { + log.verbose(`Agent Downloads cache directory was cleaned up and the following ${ + results.deleted.length + } were deleted: +${results.deleted.join('\n')} +`); + } + }); + return { hostname: vm.vmName, agentId, @@ -143,6 +184,7 @@ interface EnrollHostWithFleetOptions { log: ToolingLog; vmName: string; agentDownloadUrl: string; + cachedAgentDownload?: DownloadedAgentInfo; fleetServerUrl: string; enrollmentToken: string; } @@ -153,16 +195,35 @@ const enrollHostWithFleet = async ({ vmName, fleetServerUrl, agentDownloadUrl, + cachedAgentDownload, enrollmentToken, }: EnrollHostWithFleetOptions): Promise<{ agentId: string }> => { const agentDownloadedFile = agentDownloadUrl.substring(agentDownloadUrl.lastIndexOf('/') + 1); const vmDirName = agentDownloadedFile.replace(/\.tar\.gz$/, ''); - await execa.command( - `multipass exec ${vmName} -- curl -L ${agentDownloadUrl} -o ${agentDownloadedFile}` - ); - await execa.command(`multipass exec ${vmName} -- tar -zxf ${agentDownloadedFile}`); - await execa.command(`multipass exec ${vmName} -- rm -f ${agentDownloadedFile}`); + if (cachedAgentDownload) { + log.verbose( + `Installing agent on host using cached download from [${cachedAgentDownload.fullFilePath}]` + ); + + // mount local folder on VM + await execa.command( + `multipass mount ${cachedAgentDownload.directory} ${vmName}:~/_agent_downloads` + ); + await execa.command( + `multipass exec ${vmName} -- tar -zxf _agent_downloads/${cachedAgentDownload.filename}` + ); + await execa.command(`multipass unmount ${vmName}:~/_agent_downloads`); + } else { + log.verbose(`downloading and installing agent from URL [${agentDownloadUrl}]`); + + // download into VM + await execa.command( + `multipass exec ${vmName} -- curl -L ${agentDownloadUrl} -o ${agentDownloadedFile}` + ); + await execa.command(`multipass exec ${vmName} -- tar -zxf ${agentDownloadedFile}`); + await execa.command(`multipass exec ${vmName} -- rm -f ${agentDownloadedFile}`); + } const agentInstallArguments = [ 'exec', diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/settings_storage.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/settings_storage.ts index d68da4bfc92b6..6fa4e762d5e9d 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/settings_storage.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/settings_storage.ts @@ -28,10 +28,8 @@ export class SettingsStorage { private dirExists: boolean = false; constructor(fileName: string, options: SettingStorageOptions = {}) { - const { - directory = join(homedir(), '.kibanaSecuritySolutionCliTools'), - defaultSettings = {} as TSettingsDef, - } = options; + const { directory = SettingsStorage.getDirectory(), defaultSettings = {} as TSettingsDef } = + options; this.options = { directory, @@ -41,7 +39,17 @@ export class SettingsStorage { this.settingsFileFullPath = join(this.options.directory, fileName); } - private async ensureExists(): Promise { + /** Returns the default path to the directory where settings are saved to. */ + public static getDirectory(): string { + return join(homedir(), '.kibanaSecuritySolutionCliTools'); + } + + /** Build a path using the root directory of where settings are saved */ + protected buildPath(path: string): string { + return join(this.options.directory, path); + } + + protected async ensureExists(): Promise { if (!this.dirExists) { await mkdir(this.options.directory, { recursive: true }); this.dirExists = true; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/elastic_endpoint.ts b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/elastic_endpoint.ts index 68ff4951f77d4..f35901e263d58 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/elastic_endpoint.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/elastic_endpoint.ts @@ -7,7 +7,6 @@ import { userInfo } from 'os'; import execa from 'execa'; -import nodeFetch from 'node-fetch'; import { AGENT_POLICY_SAVED_OBJECT_TYPE, packagePolicyRouteService, @@ -15,35 +14,14 @@ import { type UpdatePackagePolicy, } from '@kbn/fleet-plugin/common'; import chalk from 'chalk'; +import { createAndEnrollEndpointHost } from '../common/endpoint_host_services'; import { getEndpointPackageInfo } from '../../../common/endpoint/utils/package'; import { indexFleetEndpointPolicy } from '../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; -import { - fetchAgentPolicyEnrollmentKey, - fetchAgentPolicyList, - fetchFleetServerUrl, - waitForHostToEnroll, -} from '../common/fleet_services'; +import { fetchAgentPolicyList } from '../common/fleet_services'; import { getRuntimeServices } from './runtime'; import { type PolicyData, ProtectionModes } from '../../../common/endpoint/types'; import { dump } from './utils'; -interface ElasticArtifactSearchResponse { - manifest: { - 'last-update-time': string; - 'seconds-since-last-update': number; - }; - packages: { - [packageFileName: string]: { - architecture: string; - os: string[]; - type: string; - asc_url: string; - sha_url: string; - url: string; - }; - }; -} - export const enrollEndpointHost = async (): Promise => { let vmName; const { @@ -61,81 +39,26 @@ export const enrollEndpointHost = async (): Promise => { const policyId: string = policy || (await getOrCreateAgentPolicyId()); if (!policyId) { - throw new Error(`No valid policy id provide or unable to create it`); + throw new Error(`No valid policy id provided or unable to create it`); } if (!version) { throw new Error(`No 'version' specified`); } - const [fleetServerHostUrl, enrollmentToken] = await Promise.all([ - fetchFleetServerUrl(kbnClient), - fetchAgentPolicyEnrollmentKey(kbnClient, policyId), - ]); - - if (!fleetServerHostUrl) { - throw new Error(`Fleet setting does not have a Fleet Server host defined!`); - } - - if (!enrollmentToken) { - throw new Error(`No API enrollment key found for policy id [${policyId}]`); - } - vmName = `${username}-dev-${uniqueId}`; log.info(`Creating VM named: ${vmName}`); - await execa.command(`multipass launch --name ${vmName} --disk 8G`); - - log.verbose(await execa('multipass', ['info', vmName])); - - const agentDownloadUrl = await getAgentDownloadUrl(version); - const agentDownloadedFile = agentDownloadUrl.substring(agentDownloadUrl.lastIndexOf('/') + 1); - const vmDirName = agentDownloadedFile.replace(/\.tar\.gz$/, ''); - - log.info(`Downloading and installing agent`); - log.verbose(`Agent download:\n ${agentDownloadUrl}`); - - await execa.command( - `multipass exec ${vmName} -- curl -L ${agentDownloadUrl} -o ${agentDownloadedFile}` - ); - await execa.command(`multipass exec ${vmName} -- tar -zxf ${agentDownloadedFile}`); - await execa.command(`multipass exec ${vmName} -- rm -f ${agentDownloadedFile}`); - - const agentInstallArguments = [ - 'exec', - - vmName, - - '--working-directory', - `/home/ubuntu/${vmDirName}`, - - '--', - - 'sudo', - - './elastic-agent', - - 'install', - - '--insecure', - - '--force', - - '--url', - fleetServerHostUrl, - - '--enrollment-token', - enrollmentToken, - ]; - - log.info(`Enrolling elastic agent with Fleet`); - log.verbose(`Command: multipass ${agentInstallArguments.join(' ')}`); - - await execa(`multipass`, agentInstallArguments); - - log.info(`Waiting for Agent to check-in with Fleet`); - await waitForHostToEnroll(kbnClient, vmName); + await createAndEnrollEndpointHost({ + kbnClient, + log, + hostname: vmName, + agentPolicyId: policyId, + version, + useClosestVersionMatch: false, + disk: '8G', + }); log.info(`VM created using Multipass. VM Name: ${vmName} @@ -155,36 +78,6 @@ export const enrollEndpointHost = async (): Promise => { return vmName; }; -const getAgentDownloadUrl = async (version: string): Promise => { - const { log } = getRuntimeServices(); - const downloadArch = - { arm64: 'arm64', x64: 'x86_64' }[process.arch] ?? `UNSUPPORTED_ARCHITECTURE_${process.arch}`; - const agentFile = `elastic-agent-${version}-linux-${downloadArch}.tar.gz`; - const artifactSearchUrl = `https://artifacts-api.elastic.co/v1/search/${version}/${agentFile}`; - - log.verbose(`Retrieving elastic agent download URL from:\n ${artifactSearchUrl}`); - - const searchResult: ElasticArtifactSearchResponse = await nodeFetch(artifactSearchUrl).then( - (response) => { - if (!response.ok) { - throw new Error( - `Failed to search elastic's artifact repository: ${response.statusText} (HTTP ${response.status})` - ); - } - - return response.json(); - } - ); - - log.verbose(searchResult); - - if (!searchResult.packages[agentFile]) { - throw new Error(`Unable to find an Agent download URL for version [${version}]`); - } - - return searchResult.packages[agentFile].url; -}; - const getOrCreateAgentPolicyId = async (): Promise => { const { kbnClient, log } = getRuntimeServices(); const username = userInfo().username.toLowerCase(); From 454418ba8e1610a7a87cc2784e5b711558f1c357 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 27 Apr 2023 11:56:21 -0400 Subject: [PATCH 06/19] skip failing test suite (#155166) --- .../instrumented_events/from_the_browser/viewport_resize.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts b/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts index f5e084810a375..8523b9f5603e7 100644 --- a/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts +++ b/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts @@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const { common } = getPageObjects(['common']); - describe('Event "viewport_resize"', () => { + // Failing: See https://github.com/elastic/kibana/issues/155166 + describe.skip('Event "viewport_resize"', () => { beforeEach(async () => { // Navigating to `home` with the Welcome prompt because some runs were flaky // as we handle the Welcome screen only if the login prompt pops up. From c6b6e28f2f005945e7a9f8871c3c053b0f2b9144 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Thu, 27 Apr 2023 17:57:08 +0200 Subject: [PATCH 07/19] [Discover] Remove legacy field stats (#155503) Closes https://github.com/elastic/kibana/issues/154841 ## Summary This PR removes legacy field stats which were shown in field popover before. They were based on loaded hits and were less precise than the current default field stats (they are based now on 5000 documents per shard sample). Before (opt-in option): Screenshot 2023-04-21 at 14 26 57 After: Screenshot 2023-04-21 at 14 26 20 --- src/plugins/discover/common/index.ts | 1 - .../discover_field_bucket.scss | 4 - .../discover_field_bucket.tsx | 109 ---------- .../discover_field_details.test.tsx | 110 ---------- .../discover_field_details.tsx | 117 ---------- .../deprecated_stats/field_calculator.test.ts | 199 ------------------ .../deprecated_stats/field_calculator.ts | 138 ------------ .../sidebar/deprecated_stats/get_details.ts | 33 --- .../deprecated_stats/string_progress_bar.tsx | 22 -- .../sidebar/deprecated_stats/types.ts | 27 --- .../sidebar/discover_field.test.tsx | 67 +----- .../components/sidebar/discover_field.tsx | 45 +--- .../components/sidebar/discover_sidebar.tsx | 6 - src/plugins/discover/server/ui_settings.ts | 20 -- .../server/collectors/management/schema.ts | 4 - .../server/collectors/management/types.ts | 1 - src/plugins/telemetry/schema/oss_plugins.json | 6 - .../_scripted_fields_classic_table.ts | 12 +- .../translations/translations/fr-FR.json | 11 - .../translations/translations/ja-JP.json | 11 - .../translations/translations/zh-CN.json | 11 - 21 files changed, 14 insertions(+), 940 deletions(-) delete mode 100644 src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_bucket.scss delete mode 100644 src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_bucket.tsx delete mode 100644 src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.test.tsx delete mode 100644 src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.tsx delete mode 100644 src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/field_calculator.test.ts delete mode 100644 src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/field_calculator.ts delete mode 100644 src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/get_details.ts delete mode 100644 src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/string_progress_bar.tsx delete mode 100644 src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/types.ts diff --git a/src/plugins/discover/common/index.ts b/src/plugins/discover/common/index.ts index 97180412d67fb..7cfec194603dd 100644 --- a/src/plugins/discover/common/index.ts +++ b/src/plugins/discover/common/index.ts @@ -28,7 +28,6 @@ export const TRUNCATE_MAX_HEIGHT = 'truncate:maxHeight'; export const ROW_HEIGHT_OPTION = 'discover:rowHeightOption'; export const SEARCH_EMBEDDABLE_TYPE = 'search'; export const HIDE_ANNOUNCEMENTS = 'hideAnnouncements'; -export const SHOW_LEGACY_FIELD_TOP_VALUES = 'discover:showLegacyFieldTopValues'; export const ENABLE_SQL = 'discover:enableSql'; export { DISCOVER_APP_LOCATOR, DiscoverAppLocatorDefinition } from './locator'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_bucket.scss b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_bucket.scss deleted file mode 100644 index 90b645f70084e..0000000000000 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_bucket.scss +++ /dev/null @@ -1,4 +0,0 @@ -.dscFieldDetails__barContainer { - // Constrains value to the flex item, and allows for truncation when necessary - min-width: 0; -} diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_bucket.tsx b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_bucket.tsx deleted file mode 100644 index 47808d14e1cc3..0000000000000 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_bucket.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { EuiText, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { DataViewField } from '@kbn/data-views-plugin/public'; -import { StringFieldProgressBar } from './string_progress_bar'; -import { Bucket } from './types'; -import './discover_field_bucket.scss'; - -interface Props { - bucket: Bucket; - field: DataViewField; - onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void; -} - -export function DiscoverFieldBucket({ field, bucket, onAddFilter }: Props) { - const emptyTxt = i18n.translate('discover.fieldChooser.detailViews.emptyStringText', { - defaultMessage: 'Empty string', - }); - const addLabel = i18n.translate('discover.fieldChooser.detailViews.filterValueButtonAriaLabel', { - defaultMessage: 'Filter for {field}: "{value}"', - values: { value: bucket.value, field: field.name }, - }); - const removeLabel = i18n.translate( - 'discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel', - { - defaultMessage: 'Filter out {field}: "{value}"', - values: { value: bucket.value, field: field.name }, - } - ); - - return ( - <> - - - - - - {bucket.display === '' ? emptyTxt : bucket.display} - - - - - {bucket.percent}% - - - - - - {onAddFilter && field.filterable && ( - -
- onAddFilter(field, bucket.value, '+')} - aria-label={addLabel} - data-test-subj={`plus-${field.name}-${bucket.value}`} - style={{ - minHeight: 'auto', - minWidth: 'auto', - paddingRight: 2, - paddingLeft: 2, - paddingTop: 0, - paddingBottom: 0, - }} - /> - onAddFilter(field, bucket.value, '-')} - aria-label={removeLabel} - data-test-subj={`minus-${field.name}-${bucket.value}`} - style={{ - minHeight: 'auto', - minWidth: 'auto', - paddingTop: 0, - paddingBottom: 0, - paddingRight: 2, - paddingLeft: 2, - }} - /> -
-
- )} -
- - - ); -} diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.test.tsx deleted file mode 100644 index 17945e7e3e027..0000000000000 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.test.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { ReactWrapper } from 'enzyme'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { EuiLoadingSpinner } from '@elastic/eui'; -import { act } from 'react-dom/test-utils'; -import { DiscoverFieldDetails } from './discover_field_details'; -import { DataViewField } from '@kbn/data-views-plugin/public'; -import { stubDataView, stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; -import { BehaviorSubject } from 'rxjs'; -import { FetchStatus } from '../../../../types'; -import { DataDocuments$ } from '../../../services/discover_data_state_container'; -import { getDataTableRecords } from '../../../../../__fixtures__/real_hits'; - -describe('discover sidebar field details', function () { - const onAddFilter = jest.fn(); - const defaultProps = { - dataView: stubDataView, - details: { buckets: [], error: '', exists: 1, total: 2, columns: [] }, - onAddFilter, - }; - const hits = getDataTableRecords(stubLogstashDataView); - const documents$ = new BehaviorSubject({ - fetchStatus: FetchStatus.COMPLETE, - result: hits, - }) as DataDocuments$; - - function mountComponent(field: DataViewField) { - const compProps = { ...defaultProps, field, documents$ }; - return mountWithIntl(); - } - - it('click on addFilter calls the function', function () { - const visualizableField = new DataViewField({ - name: 'bytes', - type: 'number', - esTypes: ['long'], - count: 10, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }); - const component = mountComponent(visualizableField); - const onAddButton = findTestSubject(component, 'onAddFilterButton'); - onAddButton.simulate('click'); - expect(onAddFilter).toHaveBeenCalledWith('_exists_', visualizableField.name, '+'); - }); - - it('should stay in sync with documents$ state', async function () { - const testDocuments$ = new BehaviorSubject({ - fetchStatus: FetchStatus.LOADING, - }) as DataDocuments$; - const visualizableField = new DataViewField({ - name: 'bytes', - type: 'number', - esTypes: ['long'], - count: 10, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }); - let component: ReactWrapper; - - await act(async () => { - component = await mountWithIntl( - - ); - }); - - expect(component!.find(EuiLoadingSpinner).exists()).toBeTruthy(); - - await act(async () => { - testDocuments$.next({ - fetchStatus: FetchStatus.COMPLETE, - result: hits, - }); - }); - - await component!.update(); - - expect(component!.find(EuiLoadingSpinner).exists()).toBeFalsy(); - expect( - findTestSubject(component!, `discoverFieldDetails-${visualizableField.name}`).exists() - ).toBeTruthy(); - - await act(async () => { - testDocuments$.next({ - fetchStatus: FetchStatus.UNINITIALIZED, - }); - }); - - await component!.update(); - - expect(component!.isEmptyRender()).toBeTruthy(); - }); -}); diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.tsx b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.tsx deleted file mode 100644 index aa027376d9155..0000000000000 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.tsx +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useEffect, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiLink, EuiSpacer, EuiText, EuiTitle, EuiLoadingSpinner } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; -import { DiscoverFieldBucket } from './discover_field_bucket'; -import { Bucket, FieldDetails } from './types'; -import { getDetails, isValidFieldDetails } from './get_details'; -import { FetchStatus } from '../../../../types'; -import { DataDocuments$ } from '../../../services/discover_data_state_container'; - -interface DiscoverFieldDetailsProps { - /** - * hits fetched from ES, displayed in the doc table - */ - documents$: DataDocuments$; - field: DataViewField; - dataView: DataView; - onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void; -} - -export function DiscoverFieldDetails({ - documents$, - field, - dataView, - onAddFilter, -}: DiscoverFieldDetailsProps) { - const [detailsState, setDetailsState] = useState<{ - details?: FieldDetails; - loaded: boolean; - }>(); - - useEffect(() => { - const subscription = documents$.subscribe((data) => { - if (data.fetchStatus === FetchStatus.COMPLETE) { - setDetailsState({ details: getDetails(field, data.result, dataView), loaded: true }); - } else { - setDetailsState({ details: undefined, loaded: data.fetchStatus !== FetchStatus.LOADING }); - } - }); - - return () => { - subscription.unsubscribe(); - }; - }, [documents$, setDetailsState, dataView, field]); - - if (!detailsState?.loaded) { - return ; - } - - const details = detailsState?.details; - if (!details) { - return null; - } - - return ( -
- -
- {i18n.translate('discover.fieldChooser.discoverField.fieldTopValuesLabel', { - defaultMessage: 'Top 5 values', - })} -
-
- {!isValidFieldDetails(details) && {details.error}} - {isValidFieldDetails(details) && ( - <> -
- {details.buckets.map((bucket: Bucket, idx: number) => ( - - ))} -
- - - {onAddFilter && !dataView.metaFields.includes(field.name) && !field.scripted ? ( - onAddFilter('_exists_', field.name, '+')} - data-test-subj="onAddFilterButton" - > - - - ) : ( - - )} - - - )} -
- ); -} diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/field_calculator.test.ts b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/field_calculator.test.ts deleted file mode 100644 index ef397acc63aba..0000000000000 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/field_calculator.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { keys, clone, uniq, filter, map } from 'lodash'; -import { getDataTableRecords } from '../../../../../__fixtures__/real_hits'; -import { fieldCalculator, FieldCountsParams } from './field_calculator'; -import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; -import { FieldDetails, ValidFieldDetails } from './types'; -import { isValidFieldDetails } from './get_details'; - -const validateResults = ( - extensions: FieldDetails, - validate: (extensions: ValidFieldDetails) => void -) => { - if (isValidFieldDetails(extensions)) { - validate(extensions); - } else { - throw new Error('extensions is not valid'); - } -}; - -describe('fieldCalculator', function () { - it('should have a _countMissing that counts nulls & undefineds in an array', function () { - const values = [ - ['foo', 'bar'], - 'foo', - 'foo', - undefined, - ['foo', 'bar'], - 'bar', - 'baz', - null, - null, - null, - 'foo', - undefined, - ]; - expect(fieldCalculator._countMissing(values)).toBe(5); - }); - - describe('_groupValues', function () { - let groups: Record; - let params: any; - let values: any; - beforeEach(function () { - values = [ - ['foo', 'bar'], - 'foo', - 'foo', - undefined, - ['foo', 'bar'], - 'bar', - 'baz', - null, - null, - null, - 'foo', - undefined, - ]; - params = {}; - groups = fieldCalculator._groupValues(values, params); - }); - - it('should have a _groupValues that counts values', function () { - expect(groups).toBeInstanceOf(Object); - }); - - it('should throw an error if any value is a plain object', function () { - expect(function () { - fieldCalculator._groupValues([{}, true, false], params); - }).toThrowError(); - }); - - it('should handle values with dots in them', function () { - values = ['0', '0.........', '0.......,.....']; - params = {}; - groups = fieldCalculator._groupValues(values, params); - expect(groups[values[0]].count).toBe(1); - expect(groups[values[1]].count).toBe(1); - expect(groups[values[2]].count).toBe(1); - }); - - it('should have a a key for value in the array when not grouping array terms', function () { - expect(keys(groups).length).toBe(3); - expect(groups.foo).toBeInstanceOf(Object); - expect(groups.bar).toBeInstanceOf(Object); - expect(groups.baz).toBeInstanceOf(Object); - }); - - it('should count array terms independently', function () { - expect(groups['foo,bar']).toBe(undefined); - expect(groups.foo.count).toBe(5); - expect(groups.bar.count).toBe(3); - expect(groups.baz.count).toBe(1); - }); - - describe('grouped array terms', function () { - beforeEach(function () { - params.grouped = true; - groups = fieldCalculator._groupValues(values, params); - }); - - it('should group array terms when passed params.grouped', function () { - expect(keys(groups).length).toBe(4); - expect(groups['foo,bar']).toBeInstanceOf(Object); - }); - - it('should contain the original array as the value', function () { - expect(groups['foo,bar'].value).toEqual(['foo', 'bar']); - }); - - it('should count the pairs separately from the values they contain', function () { - expect(groups['foo,bar'].count).toBe(2); - expect(groups.foo.count).toBe(3); - expect(groups.bar.count).toBe(1); - }); - }); - }); - - describe('getFieldValues', function () { - let hits: any; - - beforeEach(function () { - hits = getDataTableRecords(dataView); - }); - - it('Should return an array of values for _source fields', function () { - const extensions = fieldCalculator.getFieldValues( - hits, - dataView.fields.getByName('extension')! - ); - expect(extensions).toBeInstanceOf(Array); - expect(filter(extensions, (v) => v === 'html').length).toBe(8); - expect(uniq(clone(extensions)).sort()).toEqual(['gif', 'html', 'php', 'png']); - }); - - it('Should return an array of values for core meta fields', function () { - const types = fieldCalculator.getFieldValues(hits, dataView.fields.getByName('_id')!); - expect(types).toBeInstanceOf(Array); - expect(types.length).toBe(20); - }); - }); - - describe('getFieldValueCounts', function () { - let params: FieldCountsParams; - beforeEach(function () { - params = { - hits: getDataTableRecords(dataView), - field: dataView.fields.getByName('extension')!, - count: 3, - dataView, - }; - }); - - it('counts the top 3 values', function () { - validateResults(fieldCalculator.getFieldValueCounts(params), (extensions) => { - expect(extensions).toBeInstanceOf(Object); - expect(extensions.buckets).toBeInstanceOf(Array); - expect(extensions.buckets.length).toBe(3); - expect(map(extensions.buckets, 'value')).toEqual(['html', 'php', 'gif']); - }); - }); - - it('fails to analyze geo and attachment types', function () { - params.field = dataView.fields.getByName('point')!; - expect(isValidFieldDetails(fieldCalculator.getFieldValueCounts(params))).toBeFalsy(); - - params.field = dataView.fields.getByName('area')!; - expect(isValidFieldDetails(fieldCalculator.getFieldValueCounts(params))).toBeFalsy(); - - params.field = dataView.fields.getByName('request_body')!; - expect(isValidFieldDetails(fieldCalculator.getFieldValueCounts(params))).toBeFalsy(); - }); - - it('fails to analyze fields that are in the mapping, but not the hits', function () { - params.field = dataView.fields.getByName('ip')!; - expect(isValidFieldDetails(fieldCalculator.getFieldValueCounts(params))).toBeFalsy(); - }); - - it('counts the total hits', function () { - validateResults(fieldCalculator.getFieldValueCounts(params), (extensions) => { - expect(extensions.total).toBe(params.hits.length); - }); - }); - - it('counts the hits the field exists in', function () { - params.field = dataView.fields.getByName('phpmemory')!; - validateResults(fieldCalculator.getFieldValueCounts(params), (extensions) => { - expect(extensions.exists).toBe(5); - }); - }); - }); -}); diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/field_calculator.ts b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/field_calculator.ts deleted file mode 100644 index eff4ff793b2e4..0000000000000 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/field_calculator.ts +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { map, sortBy, without, each, defaults, isObject } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import type { DataViewField, DataView } from '@kbn/data-views-plugin/common'; -import type { DataTableRecord } from '../../../../../types'; -import { Bucket, FieldDetails } from './types'; - -export interface FieldCountsParams { - hits: DataTableRecord[]; - field: DataViewField; - dataView: DataView; - count?: number; - grouped?: boolean; -} - -interface FieldCountsBucket { - count: number; - value: string; -} - -const getFieldValues = (hits: DataTableRecord[], field: DataViewField): unknown[] => - map(hits, (hit) => hit.flattened[field.name]); - -const getFieldValueCounts = (params: FieldCountsParams): FieldDetails => { - params = defaults(params, { - count: 5, - grouped: false, - }); - - if ( - params.field.type === 'geo_point' || - params.field.type === 'geo_shape' || - params.field.type === 'attachment' - ) { - return { - error: i18n.translate( - 'discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage', - { - defaultMessage: 'Analysis is not available for geo fields.', - } - ), - }; - } - - const allValues = getFieldValues(params.hits, params.field); - const missing = _countMissing(allValues); - - try { - const groups = _groupValues(allValues, params); - const counts: Bucket[] = sortBy(groups, 'count') - .reverse() - .slice(0, params.count) - .map((bucket: FieldCountsBucket) => ({ - value: bucket.value, - count: bucket.count as number, - percent: Number(((bucket.count / (params.hits.length - missing)) * 100).toFixed(1)), - display: params.dataView.getFormatterForField(params.field).convert(bucket.value), - })); - - if (params.hits.length - missing === 0) { - return { - error: i18n.translate( - 'discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage', - { - defaultMessage: - 'This field is present in your Elasticsearch mapping but not in the {hitsLength} documents shown in the doc table. You may still be able to visualize or search on it.', - values: { - hitsLength: params.hits.length, - }, - } - ), - }; - } - - return { - total: params.hits.length, - exists: params.hits.length - missing, - missing, - buckets: counts, - }; - } catch (e) { - return { error: e.message }; - } -}; - -// returns a count of fields in the array that are undefined or null -const _countMissing = (array: unknown[]) => array.length - without(array, undefined, null).length; - -const _groupValues = (allValues: unknown[], params: FieldCountsParams) => { - const groups: Record = {}; - let k; - - allValues.forEach((value: unknown) => { - if (isObject(value) && !Array.isArray(value)) { - throw new Error( - i18n.translate( - 'discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage', - { - defaultMessage: 'Analysis is not available for object fields.', - } - ) - ); - } - - if (Array.isArray(value) && !params.grouped) { - k = value; - } else { - k = value == null ? undefined : [value]; - } - - each(k, (key: string) => { - if (groups.hasOwnProperty(key)) { - (groups[key] as FieldCountsBucket).count++; - } else { - groups[key] = { - value: params.grouped ? (value as string) : key, - count: 1, - }; - } - }); - }); - - return groups; -}; - -export const fieldCalculator = { - _groupValues, - _countMissing, - getFieldValues, - getFieldValueCounts, -}; diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/get_details.ts b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/get_details.ts deleted file mode 100644 index d62d3e10dd347..0000000000000 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/get_details.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; -import { fieldCalculator } from './field_calculator'; -import { DataTableRecord } from '../../../../../types'; -import { ErrorFieldDetails, FieldDetails, ValidFieldDetails } from './types'; - -export const isValidFieldDetails = (details: FieldDetails): details is ValidFieldDetails => - !(details as ErrorFieldDetails).error; - -export function getDetails( - field: DataViewField, - hits: DataTableRecord[] | undefined, - dataView: DataView -) { - if (!hits) { - return undefined; - } - - return fieldCalculator.getFieldValueCounts({ - hits, - field, - count: 5, - grouped: false, - dataView, - }); -} diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/string_progress_bar.tsx b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/string_progress_bar.tsx deleted file mode 100644 index 34bcbfcaf113e..0000000000000 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/string_progress_bar.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { EuiProgress } from '@elastic/eui'; - -interface Props { - percent: number; - count: number; - value: string; -} - -export function StringFieldProgressBar({ value, percent, count }: Props) { - const ariaLabel = `${value}: ${count} (${percent}%)`; - - return ; -} diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/types.ts b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/types.ts deleted file mode 100644 index ba308d8e14bf0..0000000000000 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export interface ValidFieldDetails { - exists: number; - total: number; - missing: number; - buckets: Bucket[]; -} - -export interface ErrorFieldDetails { - error: string; -} - -export type FieldDetails = ValidFieldDetails | ErrorFieldDetails; - -export interface Bucket { - display: string; - value: string; - percent: number; - count: number; -} diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx index 778de3c3e8f94..83ccd775bc677 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx @@ -9,7 +9,6 @@ import { act } from 'react-dom/test-utils'; import { EuiButtonIcon, EuiPopover, EuiProgress } from '@elastic/eui'; import React from 'react'; -import { BehaviorSubject } from 'rxjs'; import { findTestSubject } from '@elastic/eui/lib/test'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DiscoverField, DiscoverFieldProps } from './discover_field'; @@ -18,15 +17,9 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { stubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { DiscoverAppStateProvider } from '../../services/discover_app_state_container'; import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'; -import { FetchStatus } from '../../../types'; -import { DataDocuments$ } from '../../services/discover_data_state_container'; -import { getDataTableRecords } from '../../../../__fixtures__/real_hits'; -import * as DetailsUtil from './deprecated_stats/get_details'; import { createDiscoverServicesMock } from '../../../../__mocks__/services'; import { FieldItemButton } from '@kbn/unified-field-list-plugin/public'; -jest.spyOn(DetailsUtil, 'getDetails'); - jest.mock('@kbn/unified-field-list-plugin/public/services/field_stats', () => ({ loadFieldStats: jest.fn().mockResolvedValue({ totalDocuments: 1624, @@ -57,16 +50,12 @@ jest.mock('../../../../kibana_services', () => ({ async function getComponent({ selected = false, - showFieldStats = false, field, onAddFilterExists = true, - showLegacyFieldTopValues = false, }: { selected?: boolean; - showFieldStats?: boolean; field?: DataViewField; onAddFilterExists?: boolean; - showLegacyFieldTopValues?: boolean; }) { const finalField = field ?? @@ -83,21 +72,13 @@ async function getComponent({ const dataView = stubDataView; dataView.toSpec = () => ({}); - const hits = getDataTableRecords(dataView); - const documents$ = new BehaviorSubject({ - fetchStatus: FetchStatus.COMPLETE, - result: hits, - }) as DataDocuments$; - const props: DiscoverFieldProps = { - documents$, dataView: stubDataView, field: finalField, ...(onAddFilterExists && { onAddFilter: jest.fn() }), onAddField: jest.fn(), onEditField: jest.fn(), onRemoveField: jest.fn(), - showFieldStats, isSelected: selected, isEmpty: false, groupIndex: 1, @@ -116,9 +97,6 @@ async function getComponent({ if (key === 'fields:popularLimit') { return 5; } - if (key === 'discover:showLegacyFieldTopValues') { - return showLegacyFieldTopValues; - } }, }, }; @@ -141,10 +119,6 @@ async function getComponent({ } describe('discover sidebar field', function () { - beforeEach(() => { - (DetailsUtil.getDetails as jest.Mock).mockClear(); - }); - it('should allow selecting fields', async function () { const { comp, props } = await getComponent({}); findTestSubject(comp, 'fieldToggle-bytes').simulate('click'); @@ -155,33 +129,6 @@ describe('discover sidebar field', function () { findTestSubject(comp, 'fieldToggle-bytes').simulate('click'); expect(props.onRemoveField).toHaveBeenCalledWith('bytes'); }); - it('should trigger getDetails for showing the deprecated field stats', async function () { - const { comp, props } = await getComponent({ - selected: true, - showFieldStats: true, - showLegacyFieldTopValues: true, - }); - findTestSubject(comp, 'field-bytes-showDetails').simulate('click'); - expect(DetailsUtil.getDetails).toHaveBeenCalledTimes(1); - expect(findTestSubject(comp, `discoverFieldDetails-${props.field.name}`).exists()).toBeTruthy(); - }); - it('should not allow clicking on _source', async function () { - const field = new DataViewField({ - name: '_source', - type: '_source', - esTypes: ['_source'], - searchable: true, - aggregatable: true, - readFromDocValues: true, - }); - const { comp } = await getComponent({ - selected: true, - field, - showLegacyFieldTopValues: true, - }); - findTestSubject(comp, 'field-_source-showDetails').simulate('click'); - expect(DetailsUtil.getDetails).not.toHaveBeenCalledWith(); - }); it('displays warning for conflicting fields', async function () { const field = new DataViewField({ name: 'troubled_field', @@ -198,18 +145,6 @@ describe('discover sidebar field', function () { const dscField = findTestSubject(comp, 'field-troubled_field-showDetails'); expect(dscField.find('.kbnFieldButton__infoIcon').length).toEqual(1); }); - it('should not execute getDetails when rendered, since it can be expensive', async function () { - await getComponent({}); - expect(DetailsUtil.getDetails).toHaveBeenCalledTimes(0); - }); - it('should execute getDetails when show details is requested', async function () { - const { comp } = await getComponent({ - showFieldStats: true, - showLegacyFieldTopValues: true, - }); - findTestSubject(comp, 'field-bytes-showDetails').simulate('click'); - expect(DetailsUtil.getDetails).toHaveBeenCalledTimes(1); - }); it('should not enable the popover if onAddFilter is not provided', async function () { const field = new DataViewField({ name: '_source', @@ -236,7 +171,7 @@ describe('discover sidebar field', function () { searchable: true, }); - const { comp } = await getComponent({ showFieldStats: true, field, onAddFilterExists: true }); + const { comp } = await getComponent({ field, onAddFilterExists: true }); await act(async () => { const fieldItem = findTestSubject(comp, 'field-machine.os.raw-showDetails'); diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx index 01b503e9a8ca2..a6b694ca977d8 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx @@ -23,11 +23,8 @@ import { } from '@kbn/unified-field-list-plugin/public'; import { DragDrop } from '@kbn/dom-drag-drop'; import { DiscoverFieldStats } from './discover_field_stats'; -import { DiscoverFieldDetails } from './deprecated_stats/discover_field_details'; -import { useDiscoverServices } from '../../../../hooks/use_discover_services'; -import { PLUGIN_ID, SHOW_LEGACY_FIELD_TOP_VALUES } from '../../../../../common'; +import { PLUGIN_ID } from '../../../../../common'; import { getUiActions } from '../../../../kibana_services'; -import { type DataDocuments$ } from '../../services/discover_data_state_container'; interface GetCommonFieldItemButtonPropsParams { field: DataViewField; @@ -111,10 +108,6 @@ const MultiFields: React.FC = memo( ); export interface DiscoverFieldProps { - /** - * hits fetched from ES, displayed in the doc table - */ - documents$: DataDocuments$; /** * Determines whether add/remove button is displayed not only when focused */ @@ -169,10 +162,6 @@ export interface DiscoverFieldProps { */ onDeleteField?: (fieldName: string) => void; - /** - * Optionally show or hide field stats in the popover - */ - showFieldStats?: boolean; /** * Columns */ @@ -195,7 +184,6 @@ export interface DiscoverFieldProps { } function DiscoverFieldComponent({ - documents$, alwaysShowActionButton = false, field, highlight, @@ -209,12 +197,10 @@ function DiscoverFieldComponent({ multiFields, onEditField, onDeleteField, - showFieldStats, contextualFields, groupIndex, itemIndex, }: DiscoverFieldProps) { - const services = useDiscoverServices(); const [infoIsOpen, setOpen] = useState(false); const isDocumentRecord = !!onAddFilter; @@ -272,33 +258,18 @@ function DiscoverFieldComponent({ ); const renderPopover = () => { - const showLegacyFieldStats = services.uiSettings.get(SHOW_LEGACY_FIELD_TOP_VALUES); - return ( <> - {showLegacyFieldStats ? ( // TODO: Deprecate and remove after ~v8.7 - <> - {showFieldStats && ( - - )} - - ) : ( - - )} + {multiFields && ( <> - {(showFieldStats || !showLegacyFieldStats) && } + getRawRecordType(state.query) === RecordRawType.PLAIN ); - const showFieldStats = useMemo(() => viewMode === VIEW_MODE.DOCUMENT_LEVEL, [viewMode]); const [selectedFieldsState, setSelectedFieldsState] = useState( INITIAL_SELECTED_FIELDS_RESULT ); @@ -233,12 +231,10 @@ export function DiscoverSidebarComponent({ onAddField={onAddField} onRemoveField={onRemoveField} onAddFilter={onAddFilter} - documents$={documents$} trackUiMetric={trackUiMetric} multiFields={multiFieldsMap?.get(field.name)} // ideally we better calculate multifields when they are requested first from the popover onEditField={editField} onDeleteField={deleteField} - showFieldStats={showFieldStats} contextualFields={columns} groupIndex={groupIndex} itemIndex={itemIndex} @@ -256,12 +252,10 @@ export function DiscoverSidebarComponent({ onAddField, onRemoveField, onAddFilter, - documents$, trackUiMetric, multiFieldsMap, editField, deleteField, - showFieldStats, columns, selectedFieldsState.selectedFieldsMap, ] diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index d6985033cdb1d..be3dde9392f66 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -30,7 +30,6 @@ import { TRUNCATE_MAX_HEIGHT, SHOW_FIELD_STATISTICS, ROW_HEIGHT_OPTION, - SHOW_LEGACY_FIELD_TOP_VALUES, ENABLE_SQL, } from '../common'; import { DEFAULT_ROWS_PER_PAGE, ROWS_PER_PAGE_OPTIONS } from '../common/constants'; @@ -125,25 +124,6 @@ export const getUiSettings: (docLinks: DocLinksServiceSetup) => Record = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, - 'discover:showLegacyFieldTopValues': { - type: 'boolean', - _meta: { description: 'Non-default value of setting.' }, - }, 'discover:sampleSize': { type: 'long', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 245cb55368015..27244be1b2efd 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -80,7 +80,6 @@ export interface UsageStats { 'doc_table:hideTimeColumn': boolean; 'discover:sampleSize': number; 'discover:sampleRowsPerPage': number; - 'discover:showLegacyFieldTopValues': boolean; defaultColumns: string[]; 'context:defaultSize': number; 'context:tieBreakerFields': string[]; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 5d47ef2f9756c..4bfbb99d6e208 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -8636,12 +8636,6 @@ "description": "Non-default value of setting." } }, - "discover:showLegacyFieldTopValues": { - "type": "boolean", - "_meta": { - "description": "Non-default value of setting." - } - }, "discover:sampleSize": { "type": "long", "_meta": { diff --git a/test/functional/apps/management/_scripted_fields_classic_table.ts b/test/functional/apps/management/_scripted_fields_classic_table.ts index bf39bae566816..2af753aa7d18e 100644 --- a/test/functional/apps/management/_scripted_fields_classic_table.ts +++ b/test/functional/apps/management/_scripted_fields_classic_table.ts @@ -32,6 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); const filterBar = getService('filterBar'); + const docTable = getService('docTable'); const PageObjects = getPageObjects([ 'common', 'header', @@ -50,7 +51,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({}); await kibanaServer.uiSettings.update({ 'doc_table:legacy': true, - 'discover:showLegacyFieldTopValues': true, }); }); @@ -437,12 +437,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should filter by scripted field value in Discover', async function () { - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await log.debug('filter by "Sep 17, 2015 @ 23:00" in the expanded scripted field list'); - await PageObjects.discover.clickFieldListPlusFilter( - scriptedPainlessFieldName2, - '1442531297065' - ); + await PageObjects.header.waitUntilLoadingHasFinished(); + await docTable.toggleRowExpanded(); + const firstRow = await docTable.getDetailsRow(); + await docTable.addInclusiveFilter(firstRow, scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); await retry.try(async function () { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 285e5b8a1284f..5fa53e035d76b 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2142,11 +2142,6 @@ "discover.docTable.totalDocuments": "{totalDocuments} documents", "discover.dscTour.stepAddFields.description": "Cliquez sur {plusIcon} pour ajouter les champs qui vous intéressent.", "discover.dscTour.stepExpand.description": "Cliquez sur {expandIcon} pour afficher, comparer et filtrer les documents.", - "discover.fieldChooser.detailViews.existsInRecordsText": "Existe dans {value} / {totalValue} enregistrements", - "discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel": "Exclure {field} : \"{value}\"", - "discover.fieldChooser.detailViews.filterValueButtonAriaLabel": "Filtrer sur {field} : \"{value}\"", - "discover.fieldChooser.detailViews.valueOfRecordsText": "{value} / {totalValue} enregistrements", - "discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage": "Ce champ est présent dans votre mapping Elasticsearch, mais pas dans les {hitsLength} documents affichés dans le tableau des documents. Cependant, vous pouvez toujours le consulter ou effectuer une recherche dessus.", "discover.grid.copyClipboardButtonTitle": "Copier la valeur de {column}", "discover.grid.copyColumnValuesToClipboard.toastTitle": "Valeurs de la colonne \"{column}\" copiées dans le presse-papiers", "discover.grid.filterForAria": "Filtrer sur cette {value}", @@ -2204,8 +2199,6 @@ "discover.advancedSettings.sampleSizeTitle": "Lignes max. par tableau", "discover.advancedSettings.searchOnPageLoadText": "Détermine si une recherche est exécutée lors du premier chargement de Discover. Ce paramètre n'a pas d'effet lors du chargement d’une recherche enregistrée.", "discover.advancedSettings.searchOnPageLoadTitle": "Recherche au chargement de la page", - "discover.advancedSettings.showLegacyFieldStatsText": "Pour calculer les valeurs les plus élevées d'un champ dans la barre latérale en utilisant 500 au lieu de 5 000 enregistrements par partition, activez cette option.", - "discover.advancedSettings.showLegacyFieldStatsTitle": "Calcul des valeurs les plus élevées", "discover.advancedSettings.sortDefaultOrderText": "Détermine le sens de tri par défaut pour les vues de données temporelles dans l'application Discover.", "discover.advancedSettings.sortDefaultOrderTitle": "Sens de tri par défaut", "discover.advancedSettings.sortOrderAsc": "Croissant", @@ -2330,18 +2323,14 @@ "discover.embeddable.search.displayName": "rechercher", "discover.fieldChooser.addField.label": "Ajouter un champ", "discover.fieldChooser.availableFieldsTooltip": "Champs disponibles pour l'affichage dans le tableau.", - "discover.fieldChooser.detailViews.emptyStringText": "Chaîne vide", "discover.fieldChooser.discoverField.actions": "Actions", "discover.fieldChooser.discoverField.addFieldTooltip": "Ajouter le champ en tant que colonne", - "discover.fieldChooser.discoverField.fieldTopValuesLabel": "Top 5 des valeurs", "discover.fieldChooser.discoverField.multiField": "champ multiple", "discover.fieldChooser.discoverField.multiFields": "Champs multiples", "discover.fieldChooser.discoverField.multiFieldTooltipContent": "Les champs multiples peuvent avoir plusieurs valeurs.", "discover.fieldChooser.discoverField.name": "Champ", "discover.fieldChooser.discoverField.removeFieldTooltip": "Supprimer le champ du tableau", "discover.fieldChooser.discoverField.value": "Valeur", - "discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "L'analyse n'est pas disponible pour les champs géométriques.", - "discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "L'analyse n'est pas disponible pour les champs d'objet.", "discover.fieldChooser.fieldsMobileButtonLabel": "Champs", "discover.fieldChooser.filter.indexAndFieldsSectionAriaLabel": "Index et champs", "discover.fieldList.flyoutBackIcon": "Retour", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8c87e175bea63..77b406f88495a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2142,11 +2142,6 @@ "discover.docTable.totalDocuments": "{totalDocuments}ドキュメント", "discover.dscTour.stepAddFields.description": "{plusIcon}をクリックして、関心があるフィールドを追加します。", "discover.dscTour.stepExpand.description": "{expandIcon}をクリックすると、ドキュメントを表示、比較、フィルタリングできます。", - "discover.fieldChooser.detailViews.existsInRecordsText": "{value} / {totalValue}レコードに存在します", - "discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel": "{field}を除外:\"{value}\"", - "discover.fieldChooser.detailViews.filterValueButtonAriaLabel": "{field}のフィルター:\"{value}\"", - "discover.fieldChooser.detailViews.valueOfRecordsText": "{value} / {totalValue}レコード", - "discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage": "このフィールドはElasticsearchマッピングに表示されますが、ドキュメントテーブルの{hitsLength}件のドキュメントには含まれません。可視化や検索は可能な場合があります。", "discover.grid.copyClipboardButtonTitle": "{column}の値をコピー", "discover.grid.copyColumnValuesToClipboard.toastTitle": "\"{column}\"列の値がクリップボードにコピーされました", "discover.grid.filterForAria": "この{value}でフィルターを適用", @@ -2204,8 +2199,6 @@ "discover.advancedSettings.sampleSizeTitle": "テーブルごとの最大行数", "discover.advancedSettings.searchOnPageLoadText": "Discover の最初の読み込み時に検索を実行するかを制御します。この設定は、保存された検索の読み込み時には影響しません。", "discover.advancedSettings.searchOnPageLoadTitle": "ページの読み込み時の検索", - "discover.advancedSettings.showLegacyFieldStatsText": "シャードごとに5,000レコードではなく、500レコードを使用して、サイドバーのフィールドの上位の値を計算するには、このオプションをオンにします。", - "discover.advancedSettings.showLegacyFieldStatsTitle": "上位の値の計算", "discover.advancedSettings.sortDefaultOrderText": "Discover アプリのデータビューに基づく時刻のデフォルトの並べ替え方向をコントロールします。", "discover.advancedSettings.sortDefaultOrderTitle": "デフォルトの並べ替え方向", "discover.advancedSettings.sortOrderAsc": "昇順", @@ -2330,18 +2323,14 @@ "discover.embeddable.search.displayName": "検索", "discover.fieldChooser.addField.label": "フィールドを追加", "discover.fieldChooser.availableFieldsTooltip": "フィールドをテーブルに表示できます。", - "discover.fieldChooser.detailViews.emptyStringText": "空の文字列", "discover.fieldChooser.discoverField.actions": "アクション", "discover.fieldChooser.discoverField.addFieldTooltip": "フィールドを列として追加", - "discover.fieldChooser.discoverField.fieldTopValuesLabel": "トップ5の値", "discover.fieldChooser.discoverField.multiField": "複数フィールド", "discover.fieldChooser.discoverField.multiFields": "マルチフィールド", "discover.fieldChooser.discoverField.multiFieldTooltipContent": "複数フィールドにはフィールドごとに複数の値を入力できます", "discover.fieldChooser.discoverField.name": "フィールド", "discover.fieldChooser.discoverField.removeFieldTooltip": "フィールドを表から削除", "discover.fieldChooser.discoverField.value": "値", - "discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "ジオフィールドは分析できません。", - "discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "オブジェクトフィールドは分析できません。", "discover.fieldChooser.fieldsMobileButtonLabel": "フィールド", "discover.fieldChooser.filter.indexAndFieldsSectionAriaLabel": "インデックスとフィールド", "discover.fieldList.flyoutBackIcon": "戻る", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 23ecf9913249e..0f3fdbdcf15b4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2142,11 +2142,6 @@ "discover.docTable.totalDocuments": "{totalDocuments} 个文档", "discover.dscTour.stepAddFields.description": "单击 {plusIcon} 以添加您感兴趣的字段。", "discover.dscTour.stepExpand.description": "单击 {expandIcon} 以查看、比较和筛选文档。", - "discover.fieldChooser.detailViews.existsInRecordsText": "存在于 {value}/{totalValue} 条记录中", - "discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel": "筛除 {field}“{value}”", - "discover.fieldChooser.detailViews.filterValueButtonAriaLabel": "筛留 {field}“{value}”", - "discover.fieldChooser.detailViews.valueOfRecordsText": "{value}/{totalValue} 条记录", - "discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage": "此字段在您的 Elasticsearch 映射中,但不在文档表中显示的 {hitsLength} 个文档中。您可能仍能够基于它可视化或搜索。", "discover.grid.copyClipboardButtonTitle": "复制 {column} 的值", "discover.grid.copyColumnValuesToClipboard.toastTitle": "“{column}”列的值已复制到剪贴板", "discover.grid.filterForAria": "筛留此 {value}", @@ -2204,8 +2199,6 @@ "discover.advancedSettings.sampleSizeTitle": "每个表的最大行数", "discover.advancedSettings.searchOnPageLoadText": "控制在 Discover 首次加载时是否执行搜索。加载已保存搜索时,此设置无效。", "discover.advancedSettings.searchOnPageLoadTitle": "在页面加载时搜索", - "discover.advancedSettings.showLegacyFieldStatsText": "要在侧边栏中每分片使用 500 条而不是 5,000 条记录计算字段的排名最前值,请打开此选项。", - "discover.advancedSettings.showLegacyFieldStatsTitle": "排名最前值计算", "discover.advancedSettings.sortDefaultOrderText": "在 Discover 应用中控制基于时间的数据视图的默认排序方向。", "discover.advancedSettings.sortDefaultOrderTitle": "默认排序方向", "discover.advancedSettings.sortOrderAsc": "升序", @@ -2330,18 +2323,14 @@ "discover.embeddable.search.displayName": "搜索", "discover.fieldChooser.addField.label": "添加字段", "discover.fieldChooser.availableFieldsTooltip": "适用于在表中显示的字段。", - "discover.fieldChooser.detailViews.emptyStringText": "空字符串", "discover.fieldChooser.discoverField.actions": "操作", "discover.fieldChooser.discoverField.addFieldTooltip": "将字段添加为列", - "discover.fieldChooser.discoverField.fieldTopValuesLabel": "排名前 5 值", "discover.fieldChooser.discoverField.multiField": "多字段", "discover.fieldChooser.discoverField.multiFields": "多字段", "discover.fieldChooser.discoverField.multiFieldTooltipContent": "多字段的每个字段可以有多个值", "discover.fieldChooser.discoverField.name": "字段", "discover.fieldChooser.discoverField.removeFieldTooltip": "从表中移除字段", "discover.fieldChooser.discoverField.value": "值", - "discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "分析不适用于地理字段。", - "discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "分析不适用于对象字段。", "discover.fieldChooser.fieldsMobileButtonLabel": "字段", "discover.fieldChooser.filter.indexAndFieldsSectionAriaLabel": "索引和字段", "discover.fieldList.flyoutBackIcon": "返回", From 3480acddd8dd96fc936b2c9f170afed0baaa9002 Mon Sep 17 00:00:00 2001 From: Antonio Date: Thu, 27 Apr 2023 18:17:11 +0200 Subject: [PATCH 08/19] [Cases ]Add getAttachmentRemovalObject to file type. (#156031) ## Summary When deleting a file user action the message displayed should be " removed file". before Screenshot 2023-04-27 at 17 08 14 after Screenshot 2023-04-27 at 17 07 56 --- .../cases/public/components/files/file_type.test.tsx | 8 ++++++++ .../plugins/cases/public/components/files/file_type.tsx | 1 + .../cases/public/components/files/translations.tsx | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/x-pack/plugins/cases/public/components/files/file_type.test.tsx b/x-pack/plugins/cases/public/components/files/file_type.test.tsx index 8d4fd4c0eabde..8d2c782ee0aae 100644 --- a/x-pack/plugins/cases/public/components/files/file_type.test.tsx +++ b/x-pack/plugins/cases/public/components/files/file_type.test.tsx @@ -27,6 +27,7 @@ describe('getFileType', () => { icon: 'document', displayName: 'File Attachment Type', getAttachmentViewObject: expect.any(Function), + getAttachmentRemovalObject: expect.any(Function), }); }); @@ -184,4 +185,11 @@ describe('getFileType', () => { ); }); }); + + describe('getFileAttachmentRemovalObject', () => { + it('event renders the right message', async () => { + // @ts-ignore + expect(fileType.getAttachmentRemovalObject().event).toBe('removed file'); + }); + }); }); diff --git a/x-pack/plugins/cases/public/components/files/file_type.tsx b/x-pack/plugins/cases/public/components/files/file_type.tsx index 271bf3008e70e..f8b434d984861 100644 --- a/x-pack/plugins/cases/public/components/files/file_type.tsx +++ b/x-pack/plugins/cases/public/components/files/file_type.tsx @@ -103,4 +103,5 @@ export const getFileType = (): ExternalReferenceAttachmentType => ({ icon: 'document', displayName: 'File Attachment Type', getAttachmentViewObject: getFileAttachmentViewObject, + getAttachmentRemovalObject: () => ({ event: i18n.REMOVED_FILE }), }); diff --git a/x-pack/plugins/cases/public/components/files/translations.tsx b/x-pack/plugins/cases/public/components/files/translations.tsx index 4023c5b18cea8..2fd8ed28c6aa4 100644 --- a/x-pack/plugins/cases/public/components/files/translations.tsx +++ b/x-pack/plugins/cases/public/components/files/translations.tsx @@ -113,3 +113,7 @@ export const DELETE = i18n.translate('xpack.cases.caseView.files.delete', { export const DELETE_FILE_TITLE = i18n.translate('xpack.cases.caseView.files.deleteThisFile', { defaultMessage: 'Delete this file?', }); + +export const REMOVED_FILE = i18n.translate('xpack.cases.caseView.files.removedFile', { + defaultMessage: 'removed file', +}); From d4477976e6ccf4bd91bb6852d19345492524f063 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Thu, 27 Apr 2023 12:45:44 -0400 Subject: [PATCH 09/19] [Security Solution][Investigations] - Add tests and handle building block alerts (#155903) ## Summary This PR accomplishes a few things: 1. Make sure eql building block alerts show up when redirecting directly to the alerts page (demo of this fix below) 2. Bumps up the time window from 1 ms to 5 min, to make it easier to see other alerts when clearing out the filters 3. Add unit tests for the `alert_details_redirect.tsx` component 4. Adds a cypress test to test the presence of `kibana.alert.url` in an alert document and updates the config with the `publicBaseUrl` field which is needed for the field to be present. **Before the eql fix:** https://user-images.githubusercontent.com/17211684/234636355-507b33fc-5211-4b02-9818-d1ba78fee115.mov **After the eql fix:** https://user-images.githubusercontent.com/17211684/234635657-bdbdc2cf-8a3e-4e5c-a14e-04f878f09e7b.mov --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../e2e/detection_alerts/alerts_details.cy.ts | 18 +- .../hooks/flyout/use_init_flyout_url_param.ts | 34 +- .../alerts/alert_details_redirect.test.tsx | 132 + .../pages/alerts/alert_details_redirect.tsx | 6 +- .../es_archives/query_alert/data.json | 419 + .../es_archives/query_alert/mappings.json | 7904 +++++++++++++++++ 6 files changed, 8495 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx create mode 100644 x-pack/test/security_solution_cypress/es_archives/query_alert/data.json create mode 100644 x-pack/test/security_solution_cypress/es_archives/query_alert/mappings.json diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_details.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_details.cy.ts index ac52c58a37928..c995c48ad2222 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_details.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_details.cy.ts @@ -21,7 +21,7 @@ import { cleanKibana } from '../../tasks/common'; import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; import { login, visit, visitWithoutDateRange } from '../../tasks/login'; -import { getUnmappedRule, getNewRule } from '../../objects/rule'; +import { getUnmappedRule } from '../../objects/rule'; import { ALERTS_URL } from '../../urls/navigation'; import { tablePageSelector } from '../../screens/table_pagination'; import { ALERTS_COUNT } from '../../screens/alerts'; @@ -90,13 +90,13 @@ describe('Alert details flyout', () => { }); describe('Url state management', { testIsolation: false }, () => { - const testRule = getNewRule(); before(() => { cleanKibana(); + esArchiverLoad('query_alert'); login(); - createRule(testRule); visit(ALERTS_URL); waitForAlertsToPopulate(); + expandFirstAlert(); }); it('should store the flyout state in the url when it is opened', () => { @@ -118,7 +118,7 @@ describe('Alert details flyout', () => { cy.reload(); cy.get(OVERVIEW_RULE).should('be.visible'); cy.get(OVERVIEW_RULE).then((field) => { - expect(field).to.contain(testRule.name); + expect(field).to.contain('Endpoint Security'); }); }); @@ -140,5 +140,15 @@ describe('Alert details flyout', () => { cy.get(OVERVIEW_RULE).should('be.visible'); }); }); + + it('should have the `kibana.alert.url` field set', () => { + expandFirstAlert(); + openTable(); + filterBy('kibana.alert.url'); + cy.get('[data-test-subj="formatted-field-kibana.alert.url"]').should( + 'have.text', + 'http://localhost:5601/app/security/alerts/redirect/eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1?index=.alerts-security.alerts-default×tamp=2023-04-27T11:03:57.906Z' + ); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts b/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts index 875a87e46e4af..a67b605841917 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts @@ -10,10 +10,10 @@ import { useCallback, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import { - dataTableActions, dataTableSelectors, - tableDefaults, TableId, + tableDefaults, + dataTableActions, } from '@kbn/securitysolution-data-table'; import { useInitializeUrlParam } from '../../utils/global_query_string'; import { URL_PARAM_KEY } from '../use_url_state'; @@ -39,16 +39,28 @@ export const useInitFlyoutFromUrlParam = () => { }, []); const loadExpandedDetailFromUrl = useCallback(() => { - const { initialized, isLoading, totalCount } = dataTableCurrent; + const { initialized, isLoading, totalCount, additionalFilters } = dataTableCurrent; const isTableLoaded = initialized && !isLoading && totalCount > 0; - if (urlDetails && isTableLoaded) { - updateHasLoadedUrlDetails(true); - dispatch( - dataTableActions.toggleDetailPanel({ - id: TableId.alertsOnAlertsPage, - ...urlDetails, - }) - ); + if (urlDetails) { + if (!additionalFilters.showBuildingBlockAlerts) { + // We want to show building block alerts when loading the flyout in case the alert is a building block alert + dispatch( + dataTableActions.updateShowBuildingBlockAlertsFilter({ + id: TableId.alertsOnAlertsPage, + showBuildingBlockAlerts: true, + }) + ); + } + + if (isTableLoaded) { + updateHasLoadedUrlDetails(true); + dispatch( + dataTableActions.toggleDetailPanel({ + id: TableId.alertsOnAlertsPage, + ...urlDetails, + }) + ); + } } }, [dataTableCurrent, dispatch, urlDetails]); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx new file mode 100644 index 0000000000000..60ed09ab4d002 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { Router } from 'react-router-dom'; +import { AlertDetailsRedirect } from './alert_details_redirect'; +import { + createSecuritySolutionStorageMock, + mockGlobalState, + SUB_PLUGINS_REDUCER, + TestProviders, +} from '../../../common/mock'; +import { createStore } from '../../../common/store'; +import { kibanaObservable } from '@kbn/timelines-plugin/public/mock'; +import { + ALERTS_PATH, + ALERT_DETAILS_REDIRECT_PATH, + DEFAULT_ALERTS_INDEX, +} from '../../../../common/constants'; +import { mockHistory } from '../../../common/utils/route/mocks'; + +jest.mock('../../../common/lib/kibana'); + +const testAlertId = 'test-alert-id'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ + alertId: testAlertId, + }), +})); + +const testIndex = '.someTestIndex'; +const testTimestamp = '2023-04-20T12:00:00.000Z'; +const mockPathname = `${ALERT_DETAILS_REDIRECT_PATH}/${testAlertId}`; + +describe('AlertDetailsRedirect', () => { + const { storage } = createSecuritySolutionStorageMock(); + const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + afterEach(() => { + mockHistory.replace.mockClear(); + }); + + describe('with index and timestamp query parameters set', () => { + it('redirects to the expected path with the correct query parameters', () => { + const testSearch = `?index=${testIndex}×tamp=${testTimestamp}`; + const historyMock = { + ...mockHistory, + location: { + hash: '', + pathname: mockPathname, + search: testSearch, + state: '', + }, + }; + render( + + + + + + ); + + expect(historyMock.replace).toHaveBeenCalledWith({ + hash: '', + pathname: ALERTS_PATH, + search: `?query=(language:kuery,query:'_id: ${testAlertId}')&timerange=(global:(linkTo:!(timeline,socTrends),timerange:(from:'${testTimestamp}',kind:absolute,to:'2023-04-20T12:05:00.000Z')),timeline:(linkTo:!(global,socTrends),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now/d,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now/d)))&eventFlyout=(panelView:eventDetail,params:(eventId:${testAlertId},indexName:${testIndex}))`, + state: undefined, + }); + }); + }); + + describe('with only index query parameter set', () => { + it('redirects to the expected path with the default global timestamp settings', () => { + const testSearch = `?index=${testIndex}`; + const historyMock = { + ...mockHistory, + location: { + hash: '', + pathname: mockPathname, + search: testSearch, + state: '', + }, + }; + render( + + + + + + ); + + expect(historyMock.replace).toHaveBeenCalledWith({ + hash: '', + pathname: ALERTS_PATH, + search: `?query=(language:kuery,query:'_id: ${testAlertId}')&timerange=(global:(linkTo:!(timeline,socTrends),timerange:(from:'2020-07-07T08:20:18.966Z',kind:absolute,to:'2020-07-08T08:25:18.966Z')),timeline:(linkTo:!(global,socTrends),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now/d,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now/d)))&eventFlyout=(panelView:eventDetail,params:(eventId:${testAlertId},indexName:${testIndex}))`, + state: undefined, + }); + }); + }); + + describe('with no query parameters set', () => { + it('redirects to the expected path with the proper default alerts index and default global timestamp setting', () => { + const historyMock = { + ...mockHistory, + location: { + hash: '', + pathname: mockPathname, + search: '', + state: '', + }, + }; + render( + + + + + + ); + + expect(historyMock.replace).toHaveBeenCalledWith({ + hash: '', + pathname: ALERTS_PATH, + search: `?query=(language:kuery,query:'_id: ${testAlertId}')&timerange=(global:(linkTo:!(timeline,socTrends),timerange:(from:'2020-07-07T08:20:18.966Z',kind:absolute,to:'2020-07-08T08:25:18.966Z')),timeline:(linkTo:!(global,socTrends),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now/d,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now/d)))&eventFlyout=(panelView:eventDetail,params:(eventId:${testAlertId},indexName:.internal${DEFAULT_ALERTS_INDEX}-default))`, + state: undefined, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx index c44fcfdd7e509..ec8bf7c1526e3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx @@ -32,9 +32,9 @@ export const AlertDetailsRedirect = () => { // Default to the existing global timerange if we don't get this query param for whatever reason const fromTime = timestamp ?? globalTimerange.from; - // Add 1 millisecond to the alert timestamp as the alert table is non-inclusive of the end time - // So we have to extend slightly beyond the range of the timestamp of the given alert - const toTime = moment(timestamp ?? globalTimerange.to).add('1', 'millisecond'); + // Add 5 minutes to the alert timestamp as the alert table is non-inclusive of the end time + // This also provides padding time if the user clears the `_id` filter after redirect to see other alerts + const toTime = moment(timestamp ?? globalTimerange.to).add('5', 'minutes'); const timerange = encode({ global: { diff --git a/x-pack/test/security_solution_cypress/es_archives/query_alert/data.json b/x-pack/test/security_solution_cypress/es_archives/query_alert/data.json new file mode 100644 index 0000000000000..551f3a376033d --- /dev/null +++ b/x-pack/test/security_solution_cypress/es_archives/query_alert/data.json @@ -0,0 +1,419 @@ +{ + "type": "doc", + "value": { + "id": "eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1", + "index": ".internal.alerts-security.alerts-default-000001", + "source": { + "@timestamp": "2023-04-27T11:03:57.906Z", + "Endpoint": { + "capabilities": [ + "isolation", + "kill_process", + "suspend_process", + "running_processes", + "get_file", + "execute" + ], + "configuration": { + "isolation": true + }, + "policy": { + "applied": { + "endpoint_policy_version": 3, + "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A", + "name": "With Eventing", + "status": "success", + "version": 5 + } + }, + "state": { + "isolation": true + }, + "status": "enrolled" + }, + "agent": { + "id": "b563ce99-e373-4a1f-a5fe-97e956140aeb", + "type": "endpoint", + "version": "8.8.0" + }, + "data_stream": { + "dataset": "endpoint.alerts", + "namespace": "default", + "type": "logs" + }, + "dll": [ + { + "Ext": { + "compile_time": 1534424710, + "malware_classification": { + "identifier": "Whitelisted", + "score": 0, + "threshold": 0, + "version": "3.0.0" + }, + "mapped_address": 5362483200, + "mapped_size": 0 + }, + "code_signature": { + "subject_name": "Cybereason Inc", + "trusted": true + }, + "hash": { + "md5": "1f2d082566b0fc5f2c238a5180db7451", + "sha1": "ca85243c0af6a6471bdaa560685c51eefd6dbc0d", + "sha256": "8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2" + }, + "path": "C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe", + "pe": { + "architecture": "x64" + } + } + ], + "ecs": { + "version": "1.4.0" + }, + "elastic": { + "agent": { + "id": "b563ce99-e373-4a1f-a5fe-97e956140aeb" + } + }, + "event.action": "creation", + "event.agent_id_status": "auth_metadata_missing", + "event.category": "malware", + "event.code": "malicious_file", + "event.dataset": "endpoint", + "event.id": "b28993d4-8b8a-4f0f-9f54-84a89bad66ae", + "event.ingested": "2023-04-27T10:58:03Z", + "event.kind": "signal", + "event.module": "endpoint", + "event.sequence": 5826, + "event.type": "creation", + "file": { + "Ext": { + "code_signature": [ + { + "subject_name": "bad signer", + "trusted": false + } + ], + "malware_classification": { + "identifier": "endpointpe", + "score": 1, + "threshold": 0.66, + "version": "3.0.33" + }, + "quarantine_message": "fake quarantine message", + "quarantine_result": true, + "temp_file_path": "C:/temp/fake_malware.exe" + }, + "accessed": 1682752652103, + "created": 1682752652103, + "hash": { + "md5": "fake file md5", + "sha1": "fake file sha1", + "sha256": "fake file sha256" + }, + "mtime": 1682752652103, + "name": "fake_malware.exe", + "owner": "SYSTEM", + "path": "C:/fake_malware.exe", + "size": 3456 + }, + "host": { + "architecture": "wtnozeqvub", + "hostname": "Host-fwarau82er", + "id": "4260adf9-5e63-445d-92c6-e03359bcd342", + "ip": [ + "10.249.37.72", + "10.150.39.243", + "10.186.17.170" + ], + "mac": [ + "f5-f-97-dc-20-67", + "b5-56-ca-98-81-ca", + "22-86-39-4c-87-33" + ], + "name": "Host-fwarau82er", + "os": { + "Ext": { + "variant": "Darwin" + }, + "family": "Darwin", + "full": "macOS Monterey", + "name": "macOS", + "platform": "macOS", + "version": "12.6.1" + } + }, + "kibana.alert.ancestors": [ + { + "depth": 0, + "id": "vT9cwocBh3b8EMpD8lsi", + "index": ".ds-logs-endpoint.alerts-default-2023.04.27-000001", + "type": "event" + } + ], + "kibana.alert.depth": 1, + "kibana.alert.last_detected": "2023-04-27T11:03:57.993Z", + "kibana.alert.original_event.action": "creation", + "kibana.alert.original_event.agent_id_status": "auth_metadata_missing", + "kibana.alert.original_event.category": "malware", + "kibana.alert.original_event.code": "malicious_file", + "kibana.alert.original_event.dataset": "endpoint", + "kibana.alert.original_event.id": "b28993d4-8b8a-4f0f-9f54-84a89bad66ae", + "kibana.alert.original_event.ingested": "2023-04-27T10:58:03Z", + "kibana.alert.original_event.kind": "alert", + "kibana.alert.original_event.module": "endpoint", + "kibana.alert.original_event.sequence": 5826, + "kibana.alert.original_event.type": "creation", + "kibana.alert.original_time": "2023-04-29T07:17:32.103Z", + "kibana.alert.reason": "malware event with process malware writer, file fake_malware.exe, on Host-fwarau82er created medium alert Endpoint Security.", + "kibana.alert.risk_score": 47, + "kibana.alert.rule.actions": [ + ], + "kibana.alert.rule.author": [ + "Elastic" + ], + "kibana.alert.rule.category": "Custom Query Rule", + "kibana.alert.rule.consumer": "siem", + "kibana.alert.rule.created_at": "2023-04-27T10:58:27.546Z", + "kibana.alert.rule.created_by": "elastic", + "kibana.alert.rule.description": "Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.", + "kibana.alert.rule.enabled": true, + "kibana.alert.rule.exceptions_list": [ + { + "id": "endpoint_list", + "list_id": "endpoint_list", + "namespace_type": "agnostic", + "type": "endpoint" + } + ], + "kibana.alert.rule.execution.uuid": "ebf843ff-e0e1-47f8-9ed2-cc8066afbcef", + "kibana.alert.rule.false_positives": [ + ], + "kibana.alert.rule.from": "now-10m", + "kibana.alert.rule.immutable": true, + "kibana.alert.rule.indices": [ + "logs-endpoint.alerts-*" + ], + "kibana.alert.rule.interval": "5m", + "kibana.alert.rule.license": "Elastic License v2", + "kibana.alert.rule.max_signals": 10000, + "kibana.alert.rule.name": "Endpoint Security", + "kibana.alert.rule.parameters": { + "author": [ + "Elastic" + ], + "description": "Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.", + "exceptions_list": [ + { + "id": "endpoint_list", + "list_id": "endpoint_list", + "namespace_type": "agnostic", + "type": "endpoint" + } + ], + "false_positives": [ + ], + "from": "now-10m", + "immutable": true, + "index": [ + "logs-endpoint.alerts-*" + ], + "language": "kuery", + "license": "Elastic License v2", + "max_signals": 10000, + "query": "event.kind:alert and event.module:(endpoint and not endgame)\n", + "references": [ + ], + "related_integrations": [ + { + "package": "endpoint", + "version": "^8.2.0" + } + ], + "required_fields": [ + { + "ecs": true, + "name": "event.kind", + "type": "keyword" + }, + { + "ecs": true, + "name": "event.module", + "type": "keyword" + } + ], + "risk_score": 47, + "risk_score_mapping": [ + { + "field": "event.risk_score", + "operator": "equals", + "value": "" + } + ], + "rule_id": "9a1a2dae-0b5f-4c3d-8305-a268d404c306", + "rule_name_override": "message", + "setup": "", + "severity": "medium", + "severity_mapping": [ + { + "field": "event.severity", + "operator": "equals", + "severity": "low", + "value": "21" + }, + { + "field": "event.severity", + "operator": "equals", + "severity": "medium", + "value": "47" + }, + { + "field": "event.severity", + "operator": "equals", + "severity": "high", + "value": "73" + }, + { + "field": "event.severity", + "operator": "equals", + "severity": "critical", + "value": "99" + } + ], + "threat": [ + ], + "timestamp_override": "event.ingested", + "to": "now", + "type": "query", + "version": 101 + }, + "kibana.alert.rule.producer": "siem", + "kibana.alert.rule.references": [ + ], + "kibana.alert.rule.revision": 0, + "kibana.alert.rule.risk_score": 47, + "kibana.alert.rule.risk_score_mapping": [ + { + "field": "event.risk_score", + "operator": "equals", + "value": "" + } + ], + "kibana.alert.rule.rule_id": "9a1a2dae-0b5f-4c3d-8305-a268d404c306", + "kibana.alert.rule.rule_name_override": "message", + "kibana.alert.rule.rule_type_id": "siem.queryRule", + "kibana.alert.rule.severity": "medium", + "kibana.alert.rule.severity_mapping": [ + { + "field": "event.severity", + "operator": "equals", + "severity": "low", + "value": "21" + }, + { + "field": "event.severity", + "operator": "equals", + "severity": "medium", + "value": "47" + }, + { + "field": "event.severity", + "operator": "equals", + "severity": "high", + "value": "73" + }, + { + "field": "event.severity", + "operator": "equals", + "severity": "critical", + "value": "99" + } + ], + "kibana.alert.rule.tags": [ + "Elastic", + "Endpoint Security" + ], + "kibana.alert.rule.threat": [ + ], + "kibana.alert.rule.timestamp_override": "event.ingested", + "kibana.alert.rule.to": "now", + "kibana.alert.rule.type": "query", + "kibana.alert.rule.updated_at": "2023-04-27T10:58:27.546Z", + "kibana.alert.rule.updated_by": "elastic", + "kibana.alert.rule.uuid": "7015a3e2-e4ea-11ed-8c11-49608884878f", + "kibana.alert.rule.version": 101, + "kibana.alert.severity": "medium", + "kibana.alert.start": "2023-04-27T11:03:57.993Z", + "kibana.alert.status": "active", + "kibana.alert.url": "http://localhost:5601/app/security/alerts/redirect/eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1?index=.alerts-security.alerts-default×tamp=2023-04-27T11:03:57.906Z", + "kibana.alert.uuid": "eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1", + "kibana.alert.workflow_status": "open", + "kibana.space_ids": [ + "default" + ], + "kibana.version": "8.8.0", + "process": { + "Ext": { + "ancestry": [ + "qa5jgw1wr7", + "5k1hclygc6" + ], + "code_signature": [ + { + "subject_name": "bad signer", + "trusted": false + } + ], + "token": { + "domain": "NT AUTHORITY", + "integrity_level": 16384, + "integrity_level_name": "system", + "privileges": [ + { + "description": "Replace a process level token", + "enabled": false, + "name": "SeAssignPrimaryTokenPrivilege" + } + ], + "sid": "S-1-5-18", + "type": "tokenPrimary", + "user": "SYSTEM" + }, + "user": "SYSTEM" + }, + "entity_id": "nqh8ts6ves", + "entry_leader": { + "entity_id": "jnm38bel0w", + "name": "fake entry", + "pid": 791 + }, + "executable": "C:/malware.exe", + "group_leader": { + "entity_id": "jnm38bel0w", + "name": "fake leader", + "pid": 848 + }, + "hash": { + "md5": "fake md5", + "sha1": "fake sha1", + "sha256": "fake sha256" + }, + "name": "malware writer", + "parent": { + "entity_id": "qa5jgw1wr7", + "pid": 1 + }, + "pid": 2, + "session_leader": { + "entity_id": "jnm38bel0w", + "name": "fake session", + "pid": 909 + }, + "start": 1682752652103, + "uptime": 0 + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/security_solution_cypress/es_archives/query_alert/mappings.json b/x-pack/test/security_solution_cypress/es_archives/query_alert/mappings.json new file mode 100644 index 0000000000000..f346eee132104 --- /dev/null +++ b/x-pack/test/security_solution_cypress/es_archives/query_alert/mappings.json @@ -0,0 +1,7904 @@ +{ + "type": "index", + "value": { + "aliases": { + ".alerts-security.alerts-default": { + "is_write_index": true + }, + ".siem-signals-default": { + "is_write_index": false + } + }, + "index": ".internal.alerts-security.alerts-default-000001", + "mappings": { + "_meta": { + "kibana": { + "version": "8.8.0" + }, + "managed": true, + "namespace": "default" + }, + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "build": { + "properties": { + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ephemeral_id": { + "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" + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "type": "match_only_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_code": { + "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" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "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" + }, + "subdomain": { + "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": { + "type": "match_only_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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "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" + } + } + }, + "origin": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "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" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "project": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "target": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "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" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "container": { + "properties": { + "cpu": { + "properties": { + "usage": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "disk": { + "properties": { + "read": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "write": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "hash": { + "properties": { + "all": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "memory": { + "properties": { + "usage": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "network": { + "properties": { + "egress": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "ingress": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "type": "match_only_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_code": { + "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" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "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" + }, + "subdomain": { + "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": { + "type": "match_only_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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "device": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "manufacturer": { + "ignore_above": 1024, + "type": "keyword" + }, + "model": { + "properties": { + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dll": { + "properties": { + "code_signature": { + "properties": { + "digest_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + }, + "tlsh": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pehash": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "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" + } + } + }, + "ecs": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "email": { + "properties": { + "attachments": { + "properties": { + "file": { + "properties": { + "extension": { + "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" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + }, + "tlsh": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + } + } + } + }, + "type": "nested" + }, + "bcc": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cc": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "content_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "delivery_timestamp": { + "type": "date" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "from": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "local_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message_id": { + "type": "wildcard" + }, + "origination_timestamp": { + "type": "date" + }, + "reply_to": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "sender": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "subject": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "to": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "x_mailer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "type": "match_only_text" + }, + "stack_trace": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "type": "keyword" + }, + "agent_id_status": { + "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": { + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "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" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "faas": { + "properties": { + "coldstart": { + "type": "boolean" + }, + "execution": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trigger": { + "properties": { + "request_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "digest_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "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" + }, + "elf": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "byte_order": { + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "creation_date": { + "type": "date" + }, + "exports": { + "type": "flattened" + }, + "header": { + "properties": { + "abi_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "entrypoint": { + "type": "long" + }, + "object_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "os_abi": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "imports": { + "type": "flattened" + }, + "sections": { + "properties": { + "chi2": { + "type": "long" + }, + "entropy": { + "type": "long" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "physical_offset": { + "ignore_above": 1024, + "type": "keyword" + }, + "physical_size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "virtual_address": { + "type": "long" + }, + "virtual_size": { + "type": "long" + } + }, + "type": "nested" + }, + "segments": { + "properties": { + "sections": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "shared_libraries": { + "ignore_above": 1024, + "type": "keyword" + }, + "telfhash": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fork_name": { + "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" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + }, + "tlsh": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pehash": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "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" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "boot": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cpu": { + "properties": { + "usage": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "disk": { + "properties": { + "read": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "write": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "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" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "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" + }, + "network": { + "properties": { + "egress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "ingress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pid_ns_ino": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk": { + "properties": { + "calculated_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "calculated_score": { + "type": "float" + }, + "calculated_score_norm": { + "type": "float" + }, + "static_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "static_score": { + "type": "float" + }, + "static_score_norm": { + "type": "float" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + } + } + }, + "bytes": { + "type": "long" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + } + } + }, + "bytes": { + "type": "long" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "alert": { + "properties": { + "action_group": { + "type": "keyword" + }, + "ancestors": { + "properties": { + "depth": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "index": { + "type": "keyword" + }, + "rule": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "building_block_type": { + "type": "keyword" + }, + "case_ids": { + "type": "keyword" + }, + "depth": { + "type": "long" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "end": { + "type": "date" + }, + "flapping": { + "type": "boolean" + }, + "flapping_history": { + "type": "boolean" + }, + "group": { + "properties": { + "id": { + "type": "keyword" + }, + "index": { + "type": "integer" + } + } + }, + "instance": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "last_detected": { + "type": "date" + }, + "maintenance_window_ids": { + "type": "keyword" + }, + "new_terms": { + "type": "keyword" + }, + "original_event": { + "properties": { + "action": { + "type": "keyword" + }, + "agent_id_status": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "code": { + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "type": "keyword" + }, + "duration": { + "type": "keyword" + }, + "end": { + "type": "date" + }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "type": "keyword" + }, + "module": { + "type": "keyword" + }, + "original": { + "type": "keyword" + }, + "outcome": { + "type": "keyword" + }, + "provider": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + }, + "reference": { + "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" + }, + "url": { + "type": "keyword" + } + } + }, + "original_time": { + "type": "date" + }, + "reason": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "rule": { + "properties": { + "author": { + "type": "keyword" + }, + "building_block_type": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "exceptions_list": { + "type": "object" + }, + "execution": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "false_positives": { + "type": "keyword" + }, + "from": { + "type": "keyword" + }, + "immutable": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "max_signals": { + "type": "long" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "keyword" + }, + "parameters": { + "ignore_above": 4096, + "type": "flattened" + }, + "producer": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "revision": { + "type": "long" + }, + "rule_id": { + "type": "keyword" + }, + "rule_name_override": { + "type": "keyword" + }, + "rule_type_id": { + "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" + }, + "subtechnique": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "reference": { + "type": "keyword" + } + } + } + } + } + } + }, + "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" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "severity": { + "type": "keyword" + }, + "start": { + "type": "date" + }, + "status": { + "type": "keyword" + }, + "suppression": { + "properties": { + "docs_count": { + "type": "long" + }, + "end": { + "type": "date" + }, + "start": { + "type": "date" + }, + "terms": { + "properties": { + "field": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + } + } + }, + "system_status": { + "type": "keyword" + }, + "threshold_result": { + "properties": { + "cardinality": { + "properties": { + "field": { + "type": "keyword" + }, + "value": { + "type": "long" + } + } + }, + "count": { + "type": "long" + }, + "from": { + "type": "date" + }, + "terms": { + "properties": { + "field": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + } + } + }, + "time_range": { + "format": "epoch_millis||strict_date_optional_time", + "type": "date_range" + }, + "url": { + "ignore_above": 2048, + "index": false, + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "workflow_reason": { + "type": "keyword" + }, + "workflow_status": { + "type": "keyword" + }, + "workflow_user": { + "type": "keyword" + } + } + }, + "space_ids": { + "type": "keyword" + }, + "version": { + "type": "version" + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "file": { + "properties": { + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "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" + } + } + }, + "syslog": { + "properties": { + "appname": { + "ignore_above": 1024, + "type": "keyword" + }, + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "msgid": { + "ignore_above": 1024, + "type": "keyword" + }, + "priority": { + "type": "long" + }, + "procid": { + "ignore_above": 1024, + "type": "keyword" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "structured_data": { + "type": "flattened" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "message": { + "type": "match_only_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" + }, + "inner": { + "properties": { + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "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" + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "observer": { + "properties": { + "egress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "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" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "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" + } + } + }, + "orchestrator": { + "properties": { + "api_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "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" + }, + "code_signature": { + "properties": { + "digest_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "elf": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "byte_order": { + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "creation_date": { + "type": "date" + }, + "exports": { + "type": "flattened" + }, + "header": { + "properties": { + "abi_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "entrypoint": { + "type": "long" + }, + "object_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "os_abi": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "imports": { + "type": "flattened" + }, + "sections": { + "properties": { + "chi2": { + "type": "long" + }, + "entropy": { + "type": "long" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "physical_offset": { + "ignore_above": 1024, + "type": "keyword" + }, + "physical_size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "virtual_address": { + "type": "long" + }, + "virtual_size": { + "type": "long" + } + }, + "type": "nested" + }, + "segments": { + "properties": { + "sections": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "shared_libraries": { + "ignore_above": 1024, + "type": "keyword" + }, + "telfhash": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "end": { + "type": "date" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "entry_leader": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "attested_groups": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "attested_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "command_line": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "entry_meta": { + "properties": { + "source": { + "properties": { + "ip": { + "type": "ip" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "executable": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interactive": { + "type": "boolean" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "session_leader": { + "properties": { + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "start": { + "type": "date" + } + } + }, + "start": { + "type": "date" + } + } + }, + "pid": { + "type": "long" + }, + "real_group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "real_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "same_as_process": { + "type": "boolean" + }, + "saved_group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "start": { + "type": "date" + }, + "supplemental_groups": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tty": { + "properties": { + "char_device": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + } + } + } + } + }, + "user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "working_directory": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "env_vars": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "group_leader": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interactive": { + "type": "boolean" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "real_group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "real_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "same_as_process": { + "type": "boolean" + }, + "saved_group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "start": { + "type": "date" + }, + "supplemental_groups": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tty": { + "properties": { + "char_device": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + } + } + } + } + }, + "user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "working_directory": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "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" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + }, + "tlsh": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interactive": { + "type": "boolean" + }, + "io": { + "properties": { + "bytes_skipped": { + "properties": { + "length": { + "type": "long" + }, + "offset": { + "type": "long" + } + } + }, + "max_bytes_per_process_exceeded": { + "type": "boolean" + }, + "text": { + "type": "wildcard" + }, + "total_bytes_captured": { + "type": "long" + }, + "total_bytes_skipped": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "digest_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "elf": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "byte_order": { + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "creation_date": { + "type": "date" + }, + "exports": { + "type": "flattened" + }, + "header": { + "properties": { + "abi_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "entrypoint": { + "type": "long" + }, + "object_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "os_abi": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "imports": { + "type": "flattened" + }, + "sections": { + "properties": { + "chi2": { + "type": "long" + }, + "entropy": { + "type": "long" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "physical_offset": { + "ignore_above": 1024, + "type": "keyword" + }, + "physical_size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "virtual_address": { + "type": "long" + }, + "virtual_size": { + "type": "long" + } + }, + "type": "nested" + }, + "segments": { + "properties": { + "sections": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "shared_libraries": { + "ignore_above": 1024, + "type": "keyword" + }, + "telfhash": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "end": { + "type": "date" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group_leader": { + "properties": { + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "start": { + "type": "date" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + }, + "tlsh": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interactive": { + "type": "boolean" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pehash": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "real_group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "real_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved_group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "start": { + "type": "date" + }, + "supplemental_groups": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "tty": { + "properties": { + "char_device": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + } + } + } + } + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "working_directory": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pehash": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "previous": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "executable": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "real_group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "real_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved_group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "session_leader": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interactive": { + "type": "boolean" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "session_leader": { + "properties": { + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "start": { + "type": "date" + } + } + }, + "start": { + "type": "date" + } + } + }, + "pid": { + "type": "long" + }, + "real_group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "real_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "same_as_process": { + "type": "boolean" + }, + "saved_group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved_user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "start": { + "type": "date" + }, + "supplemental_groups": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tty": { + "properties": { + "char_device": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + } + } + } + } + }, + "user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "working_directory": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "start": { + "type": "date" + }, + "supplemental_groups": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "tty": { + "properties": { + "char_device": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + } + } + }, + "columns": { + "type": "long" + }, + "rows": { + "type": "long" + } + } + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "working_directory": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "type": "wildcard" + }, + "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": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "author": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "license": { + "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": { + "type": "match_only_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_code": { + "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" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "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" + }, + "subdomain": { + "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": { + "type": "match_only_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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "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" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "origin": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "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" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "target": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "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" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "signal": { + "properties": { + "ancestors": { + "properties": { + "depth": { + "path": "kibana.alert.ancestors.depth", + "type": "alias" + }, + "id": { + "path": "kibana.alert.ancestors.id", + "type": "alias" + }, + "index": { + "path": "kibana.alert.ancestors.index", + "type": "alias" + }, + "type": { + "path": "kibana.alert.ancestors.type", + "type": "alias" + } + } + }, + "depth": { + "path": "kibana.alert.depth", + "type": "alias" + }, + "group": { + "properties": { + "id": { + "path": "kibana.alert.group.id", + "type": "alias" + }, + "index": { + "path": "kibana.alert.group.index", + "type": "alias" + } + } + }, + "original_event": { + "properties": { + "action": { + "path": "kibana.alert.original_event.action", + "type": "alias" + }, + "category": { + "path": "kibana.alert.original_event.category", + "type": "alias" + }, + "code": { + "path": "kibana.alert.original_event.code", + "type": "alias" + }, + "created": { + "path": "kibana.alert.original_event.created", + "type": "alias" + }, + "dataset": { + "path": "kibana.alert.original_event.dataset", + "type": "alias" + }, + "duration": { + "path": "kibana.alert.original_event.duration", + "type": "alias" + }, + "end": { + "path": "kibana.alert.original_event.end", + "type": "alias" + }, + "hash": { + "path": "kibana.alert.original_event.hash", + "type": "alias" + }, + "id": { + "path": "kibana.alert.original_event.id", + "type": "alias" + }, + "kind": { + "path": "kibana.alert.original_event.kind", + "type": "alias" + }, + "module": { + "path": "kibana.alert.original_event.module", + "type": "alias" + }, + "outcome": { + "path": "kibana.alert.original_event.outcome", + "type": "alias" + }, + "provider": { + "path": "kibana.alert.original_event.provider", + "type": "alias" + }, + "reason": { + "path": "kibana.alert.original_event.reason", + "type": "alias" + }, + "risk_score": { + "path": "kibana.alert.original_event.risk_score", + "type": "alias" + }, + "risk_score_norm": { + "path": "kibana.alert.original_event.risk_score_norm", + "type": "alias" + }, + "sequence": { + "path": "kibana.alert.original_event.sequence", + "type": "alias" + }, + "severity": { + "path": "kibana.alert.original_event.severity", + "type": "alias" + }, + "start": { + "path": "kibana.alert.original_event.start", + "type": "alias" + }, + "timezone": { + "path": "kibana.alert.original_event.timezone", + "type": "alias" + }, + "type": { + "path": "kibana.alert.original_event.type", + "type": "alias" + } + } + }, + "original_time": { + "path": "kibana.alert.original_time", + "type": "alias" + }, + "reason": { + "path": "kibana.alert.reason", + "type": "alias" + }, + "rule": { + "properties": { + "author": { + "path": "kibana.alert.rule.author", + "type": "alias" + }, + "building_block_type": { + "path": "kibana.alert.building_block_type", + "type": "alias" + }, + "created_at": { + "path": "kibana.alert.rule.created_at", + "type": "alias" + }, + "created_by": { + "path": "kibana.alert.rule.created_by", + "type": "alias" + }, + "description": { + "path": "kibana.alert.rule.description", + "type": "alias" + }, + "enabled": { + "path": "kibana.alert.rule.enabled", + "type": "alias" + }, + "false_positives": { + "path": "kibana.alert.rule.false_positives", + "type": "alias" + }, + "from": { + "path": "kibana.alert.rule.from", + "type": "alias" + }, + "id": { + "path": "kibana.alert.rule.uuid", + "type": "alias" + }, + "immutable": { + "path": "kibana.alert.rule.immutable", + "type": "alias" + }, + "interval": { + "path": "kibana.alert.rule.interval", + "type": "alias" + }, + "license": { + "path": "kibana.alert.rule.license", + "type": "alias" + }, + "max_signals": { + "path": "kibana.alert.rule.max_signals", + "type": "alias" + }, + "name": { + "path": "kibana.alert.rule.name", + "type": "alias" + }, + "note": { + "path": "kibana.alert.rule.note", + "type": "alias" + }, + "references": { + "path": "kibana.alert.rule.references", + "type": "alias" + }, + "risk_score": { + "path": "kibana.alert.risk_score", + "type": "alias" + }, + "rule_id": { + "path": "kibana.alert.rule.rule_id", + "type": "alias" + }, + "rule_name_override": { + "path": "kibana.alert.rule.rule_name_override", + "type": "alias" + }, + "severity": { + "path": "kibana.alert.severity", + "type": "alias" + }, + "tags": { + "path": "kibana.alert.rule.tags", + "type": "alias" + }, + "threat": { + "properties": { + "framework": { + "path": "kibana.alert.rule.threat.framework", + "type": "alias" + }, + "tactic": { + "properties": { + "id": { + "path": "kibana.alert.rule.threat.tactic.id", + "type": "alias" + }, + "name": { + "path": "kibana.alert.rule.threat.tactic.name", + "type": "alias" + }, + "reference": { + "path": "kibana.alert.rule.threat.tactic.reference", + "type": "alias" + } + } + }, + "technique": { + "properties": { + "id": { + "path": "kibana.alert.rule.threat.technique.id", + "type": "alias" + }, + "name": { + "path": "kibana.alert.rule.threat.technique.name", + "type": "alias" + }, + "reference": { + "path": "kibana.alert.rule.threat.technique.reference", + "type": "alias" + }, + "subtechnique": { + "properties": { + "id": { + "path": "kibana.alert.rule.threat.technique.subtechnique.id", + "type": "alias" + }, + "name": { + "path": "kibana.alert.rule.threat.technique.subtechnique.name", + "type": "alias" + }, + "reference": { + "path": "kibana.alert.rule.threat.technique.subtechnique.reference", + "type": "alias" + } + } + } + } + } + } + }, + "timeline_id": { + "path": "kibana.alert.rule.timeline_id", + "type": "alias" + }, + "timeline_title": { + "path": "kibana.alert.rule.timeline_title", + "type": "alias" + }, + "timestamp_override": { + "path": "kibana.alert.rule.timestamp_override", + "type": "alias" + }, + "to": { + "path": "kibana.alert.rule.to", + "type": "alias" + }, + "type": { + "path": "kibana.alert.rule.type", + "type": "alias" + }, + "updated_at": { + "path": "kibana.alert.rule.updated_at", + "type": "alias" + }, + "updated_by": { + "path": "kibana.alert.rule.updated_by", + "type": "alias" + }, + "version": { + "path": "kibana.alert.rule.version", + "type": "alias" + } + } + }, + "status": { + "path": "kibana.alert.workflow_status", + "type": "alias" + }, + "threshold_result": { + "properties": { + "cardinality": { + "properties": { + "field": { + "path": "kibana.alert.threshold_result.cardinality.field", + "type": "alias" + }, + "value": { + "path": "kibana.alert.threshold_result.cardinality.value", + "type": "alias" + } + } + }, + "count": { + "path": "kibana.alert.threshold_result.count", + "type": "alias" + }, + "from": { + "path": "kibana.alert.threshold_result.from", + "type": "alias" + }, + "terms": { + "properties": { + "field": { + "path": "kibana.alert.threshold_result.terms.field", + "type": "alias" + }, + "value": { + "path": "kibana.alert.threshold_result.terms.value", + "type": "alias" + } + } + } + } + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "type": "match_only_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_code": { + "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" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "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" + }, + "subdomain": { + "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": { + "type": "match_only_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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "span": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "threat": { + "properties": { + "enrichments": { + "properties": { + "indicator": { + "properties": { + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "confidence": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "digest_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "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" + }, + "elf": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "byte_order": { + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "creation_date": { + "type": "date" + }, + "exports": { + "type": "flattened" + }, + "header": { + "properties": { + "abi_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "entrypoint": { + "type": "long" + }, + "object_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "os_abi": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "imports": { + "type": "flattened" + }, + "sections": { + "properties": { + "chi2": { + "type": "long" + }, + "entropy": { + "type": "long" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "physical_offset": { + "ignore_above": 1024, + "type": "keyword" + }, + "physical_size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "virtual_address": { + "type": "long" + }, + "virtual_size": { + "type": "long" + } + }, + "type": "nested" + }, + "segments": { + "properties": { + "sections": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "shared_libraries": { + "ignore_above": 1024, + "type": "keyword" + }, + "telfhash": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fork_name": { + "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" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + }, + "tlsh": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pehash": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "first_seen": { + "type": "date" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "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" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "last_seen": { + "type": "date" + }, + "marking": { + "properties": { + "tlp": { + "ignore_above": 1024, + "type": "keyword" + }, + "tlp_version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "modified_at": { + "type": "date" + }, + "port": { + "type": "long" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "type": "wildcard" + }, + "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" + } + } + }, + "scanner_stats": { + "type": "long" + }, + "sightings": { + "type": "long" + }, + "type": { + "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": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "original": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "type": "wildcard" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "matched": { + "properties": { + "atomic": { + "ignore_above": 1024, + "type": "keyword" + }, + "field": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "index": { + "ignore_above": 1024, + "type": "keyword" + }, + "occurred": { + "type": "date" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "feed": { + "properties": { + "dashboard_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "indicator": { + "properties": { + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "confidence": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "digest_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "exists": { + "type": "boolean" + }, + "signing_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "team_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "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" + }, + "elf": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "byte_order": { + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "creation_date": { + "type": "date" + }, + "exports": { + "type": "flattened" + }, + "header": { + "properties": { + "abi_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "entrypoint": { + "type": "long" + }, + "object_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "os_abi": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "imports": { + "type": "flattened" + }, + "sections": { + "properties": { + "chi2": { + "type": "long" + }, + "entropy": { + "type": "long" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "physical_offset": { + "ignore_above": 1024, + "type": "keyword" + }, + "physical_size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "virtual_address": { + "type": "long" + }, + "virtual_size": { + "type": "long" + } + }, + "type": "nested" + }, + "segments": { + "properties": { + "sections": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "shared_libraries": { + "ignore_above": 1024, + "type": "keyword" + }, + "telfhash": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fork_name": { + "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" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + }, + "tlsh": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pehash": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "first_seen": { + "type": "date" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_code": { + "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" + }, + "postal_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "last_seen": { + "type": "date" + }, + "marking": { + "properties": { + "tlp": { + "ignore_above": 1024, + "type": "keyword" + }, + "tlp_version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "modified_at": { + "type": "date" + }, + "port": { + "type": "long" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "type": "wildcard" + }, + "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" + } + } + }, + "scanner_stats": { + "type": "long" + }, + "sightings": { + "type": "long" + }, + "type": { + "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": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "original": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "type": "wildcard" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "software": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platforms": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "subtechnique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "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" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "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" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "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": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "original": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "type": "wildcard" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "type": "wildcard" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "changes": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "match_only_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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "effective": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "match_only_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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "match_only_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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "risk": { + "properties": { + "calculated_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "calculated_score": { + "type": "float" + }, + "calculated_score_norm": { + "type": "float" + }, + "static_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "static_score": { + "type": "float" + }, + "static_score_norm": { + "type": "float" + } + } + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + }, + "target": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "match_only_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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "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": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "match_only_text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "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": { + "type": "match_only_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": { + "auto_expand_replicas": "0-1", + "hidden": "true", + "lifecycle": { + "name": ".alerts-ilm-policy", + "rollover_alias": ".alerts-security.alerts-default" + }, + "mapping": { + "total_fields": { + "limit": "2500" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file From dcc280b5d81109e10ad8c738c33de2d8c19f6f90 Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Thu, 27 Apr 2023 11:50:13 -0500 Subject: [PATCH 10/19] [ML] Fix retention policy date field should list destination index date fields (#155765) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../step_details/step_details_form.tsx | 6 ++-- .../apps/transform/edit_clone/cloning.ts | 31 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index a983b91e27e40..1c756b242c79f 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -278,7 +278,9 @@ export const StepDetailsForm: FC = React.memo( // Reset retention policy settings when the user disables the whole option useEffect(() => { if (!isRetentionPolicyEnabled) { - setRetentionPolicyDateField(isRetentionPolicyAvailable ? dateFieldNames[0] : ''); + setRetentionPolicyDateField( + isRetentionPolicyAvailable ? dataViewAvailableTimeFields[0] : '' + ); setRetentionPolicyMaxAge(''); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -713,7 +715,7 @@ export const StepDetailsForm: FC = React.memo( )} > ({ text }))} + options={dataViewAvailableTimeFields.map((text: string) => ({ text }))} value={retentionPolicyDateField} onChange={(e) => setRetentionPolicyDateField(e.target.value)} data-test-subj="transformRetentionPolicyDateFieldSelect" diff --git a/x-pack/test/functional/apps/transform/edit_clone/cloning.ts b/x-pack/test/functional/apps/transform/edit_clone/cloning.ts index 6b3bbac3f7b3d..152ea8c9caa66 100644 --- a/x-pack/test/functional/apps/transform/edit_clone/cloning.ts +++ b/x-pack/test/functional/apps/transform/edit_clone/cloning.ts @@ -35,12 +35,19 @@ function getTransformConfig(): TransformPivotConfig { source: { index: ['ft_ecommerce'] }, pivot: { group_by: { category: { terms: { field: 'category.keyword' } } }, - aggregations: { 'products.base_price.avg': { avg: { field: 'products.base_price' } } }, + aggregations: { + 'products.base_price.avg': { avg: { field: 'products.base_price' } }, + 'order_date.max': { + max: { + field: 'order_date', + }, + }, + }, }, description: 'ecommerce batch transform with avg(products.base_price) grouped by terms(category)', frequency: '3s', - retention_policy: { time: { field: 'order_date', max_age: '1d' } }, + retention_policy: { time: { field: 'order_date.max', max_age: '1d' } }, settings: { max_page_search_size: 250, num_failure_retries: 0, @@ -75,11 +82,16 @@ function getTransformConfigWithRuntimeMappings(): TransformPivotConfig { 'rt_total_charge.avg': { avg: { field: 'rt_total_charge' } }, 'rt_total_charge.min': { min: { field: 'rt_total_charge' } }, 'rt_total_charge.max': { max: { field: 'rt_total_charge' } }, + max_order_date: { + max: { + field: 'order_date', + }, + }, }, }, description: 'ecommerce batch transform grouped by terms(rt_gender_lower)', frequency: '3s', - retention_policy: { time: { field: 'order_date', max_age: '3d' } }, + retention_policy: { time: { field: 'max_order_date', max_age: '3d' } }, settings: { max_page_search_size: 250, num_failure_retries: 5, @@ -155,11 +167,16 @@ function getTransformConfigWithBoolFilterAgg(): TransformPivotConfig { }, }, }, + max_order_date: { + max: { + field: 'order_date', + }, + }, }, }, description: 'ecommerce batch transform with filter aggregations', frequency: '3s', - retention_policy: { time: { field: 'order_date', max_age: '3d' } }, + retention_policy: { time: { field: 'max_order_date', max_age: '3d' } }, settings: { max_page_search_size: 250, num_failure_retries: 5, @@ -253,7 +270,7 @@ export default function ({ getService }: FtrProviderContext) { ], }, retentionPolicySwitchEnabled: true, - retentionPolicyField: 'order_date', + retentionPolicyField: 'order_date.max', retentionPolicyMaxAge: '1d', numFailureRetries: getNumFailureRetriesStr( transformConfigWithPivot.settings?.num_failure_retries @@ -288,7 +305,7 @@ export default function ({ getService }: FtrProviderContext) { values: [`female`, `male`], }, retentionPolicySwitchEnabled: true, - retentionPolicyField: 'order_date', + retentionPolicyField: 'max_order_date', retentionPolicyMaxAge: '3d', numFailureRetries: getNumFailureRetriesStr( transformConfigWithRuntimeMapping.settings?.num_failure_retries @@ -341,7 +358,7 @@ export default function ({ getService }: FtrProviderContext) { ], }, retentionPolicySwitchEnabled: true, - retentionPolicyField: 'order_date', + retentionPolicyField: 'max_order_date', retentionPolicyMaxAge: '3d', numFailureRetries: getNumFailureRetriesStr( transformConfigWithBoolFilterAgg.settings?.num_failure_retries From afb36f3fc52bad123b6406a06e3daf696d2b4c7a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Apr 2023 19:13:16 +0200 Subject: [PATCH 11/19] Update XState (main) (#150263) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 ++-- yarn.lock | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index dd4ebd2e0fc43..ca5681fea17b4 100644 --- a/package.json +++ b/package.json @@ -735,7 +735,7 @@ "@turf/distance": "6.0.1", "@turf/helpers": "6.0.1", "@turf/length": "^6.0.2", - "@xstate/react": "^3.0.2", + "@xstate/react": "^3.2.1", "JSONStream": "1.3.5", "abort-controller": "^3.0.0", "adm-zip": "^0.5.9", @@ -958,7 +958,7 @@ "vinyl": "^2.2.0", "whatwg-fetch": "^3.0.0", "xml2js": "^0.4.22", - "xstate": "^4.35.2", + "xstate": "^4.37.1", "xterm": "^5.1.0", "yauzl": "^2.10.0", "yazl": "^2.5.1" diff --git a/yarn.lock b/yarn.lock index 8d50c606a6675..7e384be955584 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9874,12 +9874,12 @@ resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== -"@xstate/react@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@xstate/react/-/react-3.0.2.tgz#7907f1e2df464c14b1cac2120a25070a0c81e111" - integrity sha512-sFbnMyi+FHF6bXbdvOF6RKVJ9VxrhhUz7KJ/8qKwpqFs0h+EoGOXdfAeuS3aEy29JsFaRclozBxDpsEJd/vlkg== +"@xstate/react@^3.2.1": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@xstate/react/-/react-3.2.2.tgz#ddf0f9d75e2c19375b1e1b7335e72cb99762aed8" + integrity sha512-feghXWLedyq8JeL13yda3XnHPZKwYDN5HPBLykpLeuNpr9178tQd2/3d0NrH6gSd0sG5mLuLeuD+ck830fgzLQ== dependencies: - use-isomorphic-layout-effect "^1.0.0" + use-isomorphic-layout-effect "^1.1.2" use-sync-external-store "^1.0.0" "@xtuc/ieee754@^1.2.0": @@ -28251,7 +28251,7 @@ use-composed-ref@^1.3.0: resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== -use-isomorphic-layout-effect@^1.0.0, use-isomorphic-layout-effect@^1.1.1: +use-isomorphic-layout-effect@^1.1.1, use-isomorphic-layout-effect@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== @@ -29627,10 +29627,10 @@ xpath@0.0.32: resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" integrity sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw== -xstate@^4.35.2: - version "4.35.2" - resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.35.2.tgz#92916638e315c448f1d26e920273187907256817" - integrity sha512-5X7EyJv5OHHtGQwN7DsmCAbSnDs3Mxl1cXQ4PVaLwi+7p/RRapERnd1dFyHjYin+KQoLLfuXpl1dPBThgyIGNg== +xstate@^4.37.1: + version "4.37.2" + resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.37.2.tgz#c5f4c1d8062784238b91e2dfddca05f821cb4eac" + integrity sha512-Qm337O49CRTZ3PRyRuK6b+kvI+D3JGxXIZCTul+xEsyFCVkTFDt5jixaL1nBWcUBcaTQ9um/5CRGVItPi7fveg== "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" From fbe3aa36b31c0e6499d03b6afb14d4d628d05312 Mon Sep 17 00:00:00 2001 From: "Mark J. Hoy" Date: Thu, 27 Apr 2023 13:22:30 -0400 Subject: [PATCH 12/19] [Enterprise Search] Add Production Model Name to Allowed List for ELSER API Endpoints (#156020) ## Summary Small PR to add the production model name to the `acceptableModelNames` list that the Enteprise Search 1-Click deploy API endpoints use to check to ensure we have a known, valid model name. ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../lib/ml/ml_model_deployment_common.ts | 2 +- .../lib/ml/start_ml_model_deployment.test.ts | 34 +++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts b/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts index f97362725bac5..9465a94301443 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts @@ -11,7 +11,7 @@ import { isResourceNotFoundException, } from '../../utils/identify_exceptions'; -export const acceptableModelNames = ['.elser_model_1_SNAPSHOT']; +export const acceptableModelNames = ['.elser_model_1', '.elser_model_1_SNAPSHOT']; export function isNotFoundExceptionError(error: unknown): boolean { return ( diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts index 827f41b83f575..ae11a89ed5ac0 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts @@ -15,7 +15,8 @@ import * as mockGetStatus from './get_ml_model_deployment_status'; import { startMlModelDeployment } from './start_ml_model_deployment'; describe('startMlModelDeployment', () => { - const knownModelName = '.elser_model_1_SNAPSHOT'; + const productionModelName = '.elser_model_1'; + const snapshotModelName = '.elser_model_1_SNAPSHOT'; const mockTrainedModelsProvider = { getTrainedModels: jest.fn(), getTrainedModelsStats: jest.fn(), @@ -27,7 +28,7 @@ describe('startMlModelDeployment', () => { }); it('should error when there is no trained model provider', () => { - expect(() => startMlModelDeployment(knownModelName, undefined)).rejects.toThrowError( + expect(() => startMlModelDeployment(productionModelName, undefined)).rejects.toThrowError( 'Machine Learning is not enabled' ); }); @@ -49,7 +50,7 @@ describe('startMlModelDeployment', () => { jest.spyOn(mockGetStatus, 'getMlModelDeploymentStatus').mockReturnValueOnce( Promise.resolve({ deploymentState: MlModelDeploymentState.Starting, - modelId: knownModelName, + modelId: productionModelName, nodeAllocationCount: 0, startTime: 123456, targetAllocationCount: 3, @@ -57,7 +58,26 @@ describe('startMlModelDeployment', () => { ); const response = await startMlModelDeployment( - knownModelName, + productionModelName, + mockTrainedModelsProvider as unknown as MlTrainedModels + ); + + expect(response.deploymentState).toEqual(MlModelDeploymentState.Starting); + }); + + it('should return the deployment state if not "downloaded" for snapshot model', async () => { + jest.spyOn(mockGetStatus, 'getMlModelDeploymentStatus').mockReturnValueOnce( + Promise.resolve({ + deploymentState: MlModelDeploymentState.Starting, + modelId: snapshotModelName, + nodeAllocationCount: 0, + startTime: 123456, + targetAllocationCount: 3, + }) + ); + + const response = await startMlModelDeployment( + snapshotModelName, mockTrainedModelsProvider as unknown as MlTrainedModels ); @@ -70,7 +90,7 @@ describe('startMlModelDeployment', () => { .mockReturnValueOnce( Promise.resolve({ deploymentState: MlModelDeploymentState.Downloaded, - modelId: knownModelName, + modelId: productionModelName, nodeAllocationCount: 0, startTime: 123456, targetAllocationCount: 3, @@ -79,7 +99,7 @@ describe('startMlModelDeployment', () => { .mockReturnValueOnce( Promise.resolve({ deploymentState: MlModelDeploymentState.Starting, - modelId: knownModelName, + modelId: productionModelName, nodeAllocationCount: 0, startTime: 123456, targetAllocationCount: 3, @@ -88,7 +108,7 @@ describe('startMlModelDeployment', () => { mockTrainedModelsProvider.startTrainedModelDeployment.mockImplementation(async () => {}); const response = await startMlModelDeployment( - knownModelName, + productionModelName, mockTrainedModelsProvider as unknown as MlTrainedModels ); expect(response.deploymentState).toEqual(MlModelDeploymentState.Starting); From e44087a06d5660690b0e31646b5c902ab115d6eb Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Thu, 27 Apr 2023 19:23:14 +0200 Subject: [PATCH 13/19] [Enterprise Search] Enable converting native connector to custom (#155853) ## Summary This adds the ability to convert a native connector to a customized connector. --------- Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> --- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + .../convert_connector_api_logic.test.ts | 32 +++++ .../connector/convert_connector_api_logic.ts | 32 +++++ .../convert_connector.tsx | 115 ++++++++++++++++++ .../convert_connector_logic.tsx | 82 +++++++++++++ .../native_connector_configuration.tsx | 6 + .../shared/doc_links/doc_links.ts | 3 + .../lib/connectors/put_update_native.ts | 27 ++++ .../routes/enterprise_search/connectors.ts | 21 ++++ 10 files changed, 320 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx create mode 100644 x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 29ac5472e37dd..89b3610e622b9 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -126,6 +126,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { apiKeys: `${KIBANA_DOCS}api-keys.html`, behavioralAnalytics: `${ENTERPRISE_SEARCH_DOCS}analytics-overview.html`, behavioralAnalyticsEvents: `${ENTERPRISE_SEARCH_DOCS}analytics-events.html`, + buildConnector: `{$ENTERPRISE_SEARCH_DOCS}build-connector.html`, bulkApi: `${ELASTICSEARCH_DOCS}docs-bulk.html`, configuration: `${ENTERPRISE_SEARCH_DOCS}configuration.html`, connectors: `${ENTERPRISE_SEARCH_DOCS}connectors.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index efe5e95f238d0..5e2e8e7bf1304 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -111,6 +111,7 @@ export interface DocLinks { readonly apiKeys: string; readonly behavioralAnalytics: string; readonly behavioralAnalyticsEvents: string; + readonly buildConnector: string; readonly bulkApi: string; readonly configuration: string; readonly connectors: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.test.ts new file mode 100644 index 0000000000000..01691488470f1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.test.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { convertConnector } from './convert_connector_api_logic'; + +describe('ConvertConnectorApilogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('convertConnector', () => { + it('calls correct api', async () => { + const promise = Promise.resolve('result'); + http.put.mockReturnValue(promise); + const result = convertConnector({ connectorId: 'connectorId1' }); + await nextTick(); + expect(http.put).toHaveBeenCalledWith( + '/internal/enterprise_search/connectors/connectorId1/native', + { body: JSON.stringify({ is_native: false }) } + ); + await expect(result).resolves.toEqual('result'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts new file mode 100644 index 0000000000000..ba998c9dbce9d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface ConvertConnectorApiLogicArgs { + connectorId: string; +} + +export interface ConvertConnectorApiLogicResponse { + updated: boolean; +} + +export const convertConnector = async ({ + connectorId, +}: ConvertConnectorApiLogicArgs): Promise => { + const route = `/internal/enterprise_search/connectors/${connectorId}/native`; + + return await HttpLogic.values.http.put<{ updated: boolean }>(route, { + body: JSON.stringify({ is_native: false }), + }); +}; + +export const ConvertConnectorApiLogic = createApiLogic( + ['convert_connector_api_logic'], + convertConnector +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx new file mode 100644 index 0000000000000..450565088e842 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiTitle, + EuiSpacer, + EuiText, + EuiLink, + EuiButton, + EuiConfirmModal, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants'; + +import { docLinks } from '../../../../../shared/doc_links'; + +import { ConvertConnectorLogic } from './convert_connector_logic'; + +export const ConvertConnector: React.FC = () => { + const { convertConnector, hideModal, showModal } = useActions(ConvertConnectorLogic); + const { isLoading, isModalVisible } = useValues(ConvertConnectorLogic); + + return ( + <> + {isModalVisible && ( + hideModal()} + onConfirm={() => convertConnector()} + title={i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.convertInfexConfirm.title', + { defaultMessage: 'Sure you want to convert your connector?' } + )} + buttonColor="danger" + cancelButtonText={CANCEL_BUTTON_LABEL} + confirmButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.convertIndexConfirm.text', + { + defaultMessage: 'Yes', + } + )} + isLoading={isLoading} + defaultFocusedButton="confirm" + maxWidth + > + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.convertIndexConfirm.description', + { + defaultMessage: + "Once you convert a native connector to a self-managed connector client this can't be undone.", + } + )} +

+
+
+ )} + + + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.title', + { + defaultMessage: 'Customize your connector', + } + )} +

+
+
+
+ + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.linkTitle', + { defaultMessage: 'connector client' } + )} + + ), + }} + /> + + showModal()}> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.buttonTitle', + { defaultMessage: 'Convert connector' } + )} + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx new file mode 100644 index 0000000000000..841f8cde106a2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { Status } from '../../../../../../../common/types/api'; +import { Actions } from '../../../../../shared/api_logic/create_api_logic'; +import { + ConvertConnectorApiLogic, + ConvertConnectorApiLogicArgs, + ConvertConnectorApiLogicResponse, +} from '../../../../api/connector/convert_connector_api_logic'; +import { IndexViewActions, IndexViewLogic } from '../../index_view_logic'; + +interface ConvertConnectorValues { + connectorId: typeof IndexViewLogic.values.connectorId; + isLoading: boolean; + isModalVisible: boolean; + status: Status; +} + +type ConvertConnectorActions = Pick< + Actions, + 'apiError' | 'apiSuccess' | 'makeRequest' +> & { + convertConnector(): void; + fetchIndex(): IndexViewActions['fetchIndex']; + hideModal(): void; + showModal(): void; +}; + +export const ConvertConnectorLogic = kea< + MakeLogicType +>({ + actions: { + convertConnector: () => true, + deleteDomain: () => true, + hideModal: () => true, + showModal: () => true, + }, + connect: { + actions: [ + ConvertConnectorApiLogic, + ['apiError', 'apiSuccess', 'makeRequest'], + IndexViewLogic, + ['fetchIndex'], + ], + values: [ConvertConnectorApiLogic, ['status'], IndexViewLogic, ['connectorId']], + }, + listeners: ({ actions, values }) => ({ + convertConnector: () => { + if (values.connectorId) { + actions.makeRequest({ connectorId: values.connectorId }); + } + }, + }), + path: ['enterprise_search', 'convert_connector_modal'], + reducers: { + isModalVisible: [ + false, + { + apiError: () => false, + apiSuccess: () => false, + hideModal: () => false, + showModal: () => true, + }, + ], + }, + selectors: ({ selectors }) => ({ + isLoading: [() => [selectors.status], (status: Status) => status === Status.LOADING], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx index c4c4af581ef5a..960bd61264ddf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx @@ -31,6 +31,7 @@ import { IndexViewLogic } from '../../index_view_logic'; import { ConnectorNameAndDescription } from '../connector_name_and_description/connector_name_and_description'; import { NATIVE_CONNECTORS } from '../constants'; +import { ConvertConnector } from './convert_connector'; import { NativeConnectorAdvancedConfiguration } from './native_connector_advanced_configuration'; import { NativeConnectorConfigurationConfig } from './native_connector_configuration_config'; import { ResearchConfiguration } from './research_configuration'; @@ -203,6 +204,11 @@ export const NativeConnectorConfiguration: React.FC = () => { + + + + +
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts index 607dc7361e2f4..bd273957d2ffa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts @@ -36,6 +36,7 @@ class DocLinks { public appSearchWebCrawlerReference: string; public behavioralAnalytics: string; public behavioralAnalyticsEvents: string; + public buildConnector: string; public bulkApi: string; public clientsGoIndex: string; public clientsGuide: string; @@ -164,6 +165,7 @@ class DocLinks { this.appSearchWebCrawlerReference = ''; this.behavioralAnalytics = ''; this.behavioralAnalyticsEvents = ''; + this.buildConnector = ''; this.bulkApi = ''; this.clientsGoIndex = ''; this.clientsGuide = ''; @@ -294,6 +296,7 @@ class DocLinks { this.appSearchWebCrawlerReference = docLinks.links.appSearch.webCrawlerReference; this.behavioralAnalytics = docLinks.links.enterpriseSearch.behavioralAnalytics; this.behavioralAnalyticsEvents = docLinks.links.enterpriseSearch.behavioralAnalyticsEvents; + this.buildConnector = docLinks.links.enterpriseSearch.buildConnector; this.bulkApi = docLinks.links.enterpriseSearch.bulkApi; this.clientsGoIndex = docLinks.links.clients.goIndex; this.clientsGuide = docLinks.links.clients.guide; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts new file mode 100644 index 0000000000000..6b3039974f8a4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; + +import { CONNECTORS_INDEX } from '../..'; +import { Connector } from '../../../common/types/connectors'; + +export const putUpdateNative = async ( + client: IScopedClusterClient, + connectorId: string, + isNative: boolean +) => { + const result = await client.asCurrentUser.update({ + doc: { + is_native: isNative, + }, + id: connectorId, + index: CONNECTORS_INDEX, + }); + + return result; +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index 32e7cb79a2597..f0c0df50f7155 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -22,6 +22,7 @@ import { fetchSyncJobsByConnectorId } from '../../lib/connectors/fetch_sync_jobs import { cancelSyncs } from '../../lib/connectors/post_cancel_syncs'; import { updateFiltering } from '../../lib/connectors/put_update_filtering'; import { updateFilteringDraft } from '../../lib/connectors/put_update_filtering_draft'; +import { putUpdateNative } from '../../lib/connectors/put_update_native'; import { startConnectorSync } from '../../lib/connectors/start_sync'; import { updateConnectorConfiguration } from '../../lib/connectors/update_connector_configuration'; import { updateConnectorNameAndDescription } from '../../lib/connectors/update_connector_name_and_description'; @@ -383,4 +384,24 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { return result ? response.ok({ body: result }) : response.conflict(); }) ); + router.put( + { + path: '/internal/enterprise_search/connectors/{connectorId}/native', + validate: { + body: schema.object({ + is_native: schema.boolean(), + }), + params: schema.object({ + connectorId: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const connectorId = decodeURIComponent(request.params.connectorId); + const { is_native } = request.body; + const result = await putUpdateNative(client, connectorId, is_native); + return result ? response.ok({ body: result }) : response.conflict(); + }) + ); } From 6c5aafba04767ea6411b5b2cd613f1ab29ecfd75 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Thu, 27 Apr 2023 19:25:54 +0200 Subject: [PATCH 14/19] Fix React Errors in SLOs (#155953) --- .../observability/public/config/paths.ts | 1 + .../__storybook_mocks__/use_fetch_slo_list.ts | 1 + .../public/hooks/slo/use_clone_slo.ts | 30 ++++- .../public/hooks/slo/use_create_slo.ts | 55 +++++++++- .../public/hooks/slo/use_delete_slo.ts | 31 ++++-- .../hooks/slo/use_fetch_rules_for_slo.ts | 3 +- .../public/hooks/slo/use_fetch_slo_list.ts | 8 +- .../public/hooks/slo/use_update_slo.ts | 26 ++++- .../slo_details/components/header_control.tsx | 42 +++---- .../pages/slo_details/slo_details.test.tsx | 70 +++++++++--- .../slo_edit/components/slo_edit_form.tsx | 70 ++++-------- .../public/pages/slo_edit/slo_edit.test.tsx | 95 ++-------------- .../slos/components/badges/slo_badges.tsx | 45 ++++++-- .../slo_delete_confirmation_modal.tsx | 91 ++++------------ .../public/pages/slos/components/slo_list.tsx | 14 ++- .../pages/slos/components/slo_list_item.tsx | 50 +++++---- .../pages/slos/components/slo_list_items.tsx | 8 ++ .../public/pages/slos/slos.test.tsx | 39 +++++-- .../observability/public/pages/slos/slos.tsx | 13 ++- .../assets/illustration.svg | 0 .../slos_welcome.stories.tsx} | 6 +- .../pages/slos_welcome/slos_welcome.test.tsx | 103 ++++++++++++++++++ .../slos_welcome.tsx} | 27 +++-- .../observability/public/routes/index.tsx | 8 ++ 24 files changed, 511 insertions(+), 325 deletions(-) rename x-pack/plugins/observability/public/pages/{slos/components => slos_welcome}/assets/illustration.svg (100%) rename x-pack/plugins/observability/public/pages/{slos/components/slo_list_welcome_prompt.stories.tsx => slos_welcome/slos_welcome.stories.tsx} (71%) create mode 100644 x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.test.tsx rename x-pack/plugins/observability/public/pages/{slos/components/slo_list_welcome_prompt.tsx => slos_welcome/slos_welcome.tsx} (88%) diff --git a/x-pack/plugins/observability/public/config/paths.ts b/x-pack/plugins/observability/public/config/paths.ts index 73430af2edbcf..8d20c22637698 100644 --- a/x-pack/plugins/observability/public/config/paths.ts +++ b/x-pack/plugins/observability/public/config/paths.ts @@ -18,6 +18,7 @@ export const paths = { ruleDetails: (ruleId?: string | null) => ruleId ? `${RULES_PAGE_LINK}/${encodeURI(ruleId)}` : RULES_PAGE_LINK, slos: SLOS_PAGE_LINK, + slosWelcome: `${SLOS_PAGE_LINK}/welcome`, sloCreate: `${SLOS_PAGE_LINK}/create`, sloEdit: (sloId: string) => `${SLOS_PAGE_LINK}/edit/${encodeURI(sloId)}`, sloDetails: (sloId: string) => `${SLOS_PAGE_LINK}/${encodeURI(sloId)}`, diff --git a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts index 3c573cb6754d5..2faa0887b82ea 100644 --- a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts +++ b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts @@ -12,6 +12,7 @@ export const useFetchSloList = (): UseFetchSloListResponse => { return { isInitialLoading: false, isLoading: false, + isRefetching: false, isError: false, isSuccess: true, sloList, diff --git a/x-pack/plugins/observability/public/hooks/slo/use_clone_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_clone_slo.ts index d2fcc25eb2c34..00fddd8aebf14 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_clone_slo.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_clone_slo.ts @@ -7,15 +7,19 @@ import { v1 as uuidv1 } from 'uuid'; import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { i18n } from '@kbn/i18n'; import type { CreateSLOInput, CreateSLOResponse, FindSLOResponse } from '@kbn/slo-schema'; import { useKibana } from '../../utils/kibana_react'; export function useCloneSlo() { - const { http } = useKibana().services; + const { + http, + notifications: { toasts }, + } = useKibana().services; const queryClient = useQueryClient(); - const cloneSlo = useMutation< + return useMutation< CreateSLOResponse, string, { slo: CreateSLOInput; idToCopyFrom?: string }, @@ -41,7 +45,10 @@ export function useCloneSlo() { const optimisticUpdate = { ...data, - results: [...(data?.results || []), { ...sloUsedToClone, name: slo.name, id: uuidv1() }], + results: [ + ...(data?.results || []), + { ...sloUsedToClone, name: slo.name, id: uuidv1(), summary: undefined }, + ], total: data?.total && data.total + 1, }; @@ -50,20 +57,31 @@ export function useCloneSlo() { queryClient.setQueryData(queryKey, optimisticUpdate); } + toasts.addSuccess( + i18n.translate('xpack.observability.slo.clone.successNotification', { + defaultMessage: 'Successfully created {name}', + values: { name: slo.name }, + }) + ); + // Return a context object with the snapshotted value return { previousSloList: data }; }, // If the mutation fails, use the context returned from onMutate to roll back - onError: (_err, _slo, context) => { + onError: (_err, { slo }, context) => { if (context?.previousSloList) { queryClient.setQueryData(['fetchSloList'], context.previousSloList); } + toasts.addDanger( + i18n.translate('xpack.observability.slo.clone.errorNotification', { + defaultMessage: 'Failed to clone {name}', + values: { name: slo.name }, + }) + ); }, onSuccess: () => { queryClient.invalidateQueries(['fetchSloList']); }, } ); - - return cloneSlo; } diff --git a/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts index d9b000fee0cd2..3b4bdf4ce61bd 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts @@ -5,27 +5,70 @@ * 2.0. */ +import { v1 as uuidv1 } from 'uuid'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import type { CreateSLOInput, CreateSLOResponse } from '@kbn/slo-schema'; +import { i18n } from '@kbn/i18n'; +import type { CreateSLOInput, CreateSLOResponse, FindSLOResponse } from '@kbn/slo-schema'; import { useKibana } from '../../utils/kibana_react'; export function useCreateSlo() { - const { http } = useKibana().services; + const { + http, + notifications: { toasts }, + } = useKibana().services; const queryClient = useQueryClient(); - const createSlo = useMutation( + return useMutation( ({ slo }: { slo: CreateSLOInput }) => { const body = JSON.stringify(slo); return http.post(`/api/observability/slos`, { body }); }, { mutationKey: ['createSlo'], - onSuccess: () => { + onSuccess: (_data, { slo: { name } }) => { + toasts.addSuccess( + i18n.translate('xpack.observability.slo.create.successNotification', { + defaultMessage: 'Successfully created {name}', + values: { name }, + }) + ); queryClient.invalidateQueries(['fetchSloList']); }, + onError: (error, { slo: { name } }) => { + toasts.addError(new Error(String(error)), { + title: i18n.translate('xpack.observability.slo.create.errorNotification', { + defaultMessage: 'Something went wrong while creating {name}', + values: { name }, + }), + }); + }, + onMutate: async ({ slo }) => { + // Cancel any outgoing refetches (so they don't overwrite our optimistic update) + await queryClient.cancelQueries(['fetchSloList']); + + const latestFetchSloListRequest = ( + queryClient.getQueriesData(['fetchSloList']) || [] + ).at(0); + + const [queryKey, data] = latestFetchSloListRequest || []; + + const newItem = { ...slo, id: uuidv1() }; + + const optimisticUpdate = { + ...data, + results: [...(data?.results || []), { ...newItem }], + total: data?.total ? data.total + 1 : 1, + }; + + // Optimistically update to the new value + if (queryKey) { + queryClient.setQueryData(queryKey, optimisticUpdate); + } + + // Return a context object with the snapshotted value + return { previousSloList: data }; + }, } ); - - return createSlo; } diff --git a/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts index f0af48585a26a..2878b3e158684 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts @@ -6,21 +6,24 @@ */ import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { i18n } from '@kbn/i18n'; import { FindSLOResponse } from '@kbn/slo-schema'; - import { useKibana } from '../../utils/kibana_react'; -export function useDeleteSlo(sloId: string) { - const { http } = useKibana().services; +export function useDeleteSlo() { + const { + http, + notifications: { toasts }, + } = useKibana().services; const queryClient = useQueryClient(); const deleteSlo = useMutation< string, string, - { id: string }, + { id: string; name: string }, { previousSloList: FindSLOResponse | undefined } >( - ['deleteSlo', sloId], + ['deleteSlo'], ({ id }) => { try { return http.delete(`/api/observability/slos/${id}`); @@ -50,16 +53,30 @@ export function useDeleteSlo(sloId: string) { queryClient.setQueryData(queryKey, optimisticUpdate); } + toasts.addSuccess( + i18n.translate('xpack.observability.slo.slo.delete.successNotification', { + defaultMessage: 'Deleted {name}', + values: { name: slo.name }, + }) + ); + // Return a context object with the snapshotted value return { previousSloList: data }; }, // If the mutation fails, use the context returned from onMutate to roll back - onError: (_err, _slo, context) => { + onError: (_err, slo, context) => { if (context?.previousSloList) { queryClient.setQueryData(['fetchSloList'], context.previousSloList); } + + toasts.addDanger( + i18n.translate('xpack.observability.slo.slo.delete.errorNotification', { + defaultMessage: 'Failed to delete {name}', + values: { name: slo.name }, + }) + ); }, - onSuccess: () => { + onSuccess: (_success, slo) => { queryClient.invalidateQueries(['fetchSloList']); }, } diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts index 153e2fb95c966..39ea6fdf994e9 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts @@ -77,8 +77,9 @@ export function useFetchRulesForSlo({ sloIds }: Params): UseFetchRulesForSloResp // ignore error for retrieving slos } }, - enabled: Boolean(sloIds), + enabled: Boolean(sloIds?.length), refetchOnWindowFocus: false, + keepPreviousData: true, } ); diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts index fa9c2761e94d8..086ec0d8e77a9 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts @@ -28,6 +28,7 @@ interface SLOListParams { export interface UseFetchSloListResponse { isInitialLoading: boolean; isLoading: boolean; + isRefetching: boolean; isSuccess: boolean; isError: boolean; sloList: FindSLOResponse | undefined; @@ -96,14 +97,19 @@ export function useFetchSloList({ queryClient.invalidateQueries(['fetchActiveAlerts'], { exact: false, }); + + queryClient.invalidateQueries(['fetchRulesForSlo'], { + exact: false, + }); }, } ); return { sloList: data, - isLoading: isLoading || isRefetching, isInitialLoading, + isLoading, + isRefetching, isSuccess, isError, refetch, diff --git a/x-pack/plugins/observability/public/hooks/slo/use_update_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_update_slo.ts index 46d3d2eddea76..5fc25f4192756 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_update_slo.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_update_slo.ts @@ -6,27 +6,43 @@ */ import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { i18n } from '@kbn/i18n'; import type { UpdateSLOInput, UpdateSLOResponse } from '@kbn/slo-schema'; import { useKibana } from '../../utils/kibana_react'; export function useUpdateSlo() { - const { http } = useKibana().services; + const { + http, + notifications: { toasts }, + } = useKibana().services; const queryClient = useQueryClient(); - const updateSlo = useMutation( + return useMutation( ({ sloId, slo }: { sloId: string; slo: UpdateSLOInput }) => { const body = JSON.stringify(slo); return http.put(`/api/observability/slos/${sloId}`, { body }); }, { mutationKey: ['updateSlo'], - onSuccess: () => { + onSuccess: (_data, { slo: { name } }) => { + toasts.addSuccess( + i18n.translate('xpack.observability.slo.update.successNotification', { + defaultMessage: 'Successfully updated {name}', + values: { name }, + }) + ); queryClient.invalidateQueries(['fetchSloList']); queryClient.invalidateQueries(['fetchHistoricalSummary']); }, + onError: (error, { slo: { name } }) => { + toasts.addError(new Error(String(error)), { + title: i18n.translate('xpack.observability.slo.update.errorNotification', { + defaultMessage: 'Something went wrong when updating {name}', + values: { name }, + }), + }); + }, } ); - - return updateSlo; } diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx index 848b35b8bb293..ace0f1a6e5167 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx @@ -5,20 +5,20 @@ * 2.0. */ -import React, { useState } from 'react'; -import { useIsMutating } from '@tanstack/react-query'; +import React, { useCallback, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { useCapabilities } from '../../../hooks/slo/use_capabilities'; import { useKibana } from '../../../utils/kibana_react'; +import { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; +import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo'; import { isApmIndicatorType } from '../../../utils/slo/indicator'; import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url'; import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants'; import { rulesLocatorID, sloFeatureId } from '../../../../common'; import { paths } from '../../../config/paths'; -import { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; import { transformSloResponseToCreateSloInput, transformValuesToCreateSLOInput, @@ -35,7 +35,6 @@ export function HeaderControl({ isLoading, slo }: Props) { const { application: { navigateToUrl }, http: { basePath }, - notifications: { toasts }, share: { url: { locators }, }, @@ -47,15 +46,15 @@ export function HeaderControl({ isLoading, slo }: Props) { const [isRuleFlyoutVisible, setRuleFlyoutVisibility] = useState(false); const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false); - const { mutateAsync: cloneSlo } = useCloneSlo(); - const isDeleting = Boolean(useIsMutating(['deleteSlo', slo?.id])); + const { mutate: cloneSlo } = useCloneSlo(); + const { mutate: deleteSlo } = useDeleteSlo(); const handleActionsClick = () => setIsPopoverOpen((value) => !value); const closePopover = () => setIsPopoverOpen(false); const handleEdit = () => { if (slo) { - navigateToUrl(basePath.prepend(paths.observability.sloEdit(slo.id))); + navigate(basePath.prepend(paths.observability.sloEdit(slo.id))); } }; @@ -104,7 +103,7 @@ export function HeaderControl({ isLoading, slo }: Props) { transactionType, }); - navigateToUrl(basePath.prepend(url)); + navigate(basePath.prepend(url)); } }; @@ -116,16 +115,9 @@ export function HeaderControl({ isLoading, slo }: Props) { transformSloResponseToCreateSloInput({ ...slo, name: `[Copy] ${slo.name}` })! ); - await cloneSlo({ slo: newSlo, idToCopyFrom: slo.id }); - - toasts.addSuccess( - i18n.translate('xpack.observability.slo.sloDetails.headerControl.cloneSuccess', { - defaultMessage: 'Successfully created {name}', - values: { name: newSlo.name }, - }) - ); + cloneSlo({ slo: newSlo, idToCopyFrom: slo.id }); - navigateToUrl(basePath.prepend(paths.observability.slos)); + navigate(basePath.prepend(paths.observability.slos)); } }; @@ -138,10 +130,18 @@ export function HeaderControl({ isLoading, slo }: Props) { setDeleteConfirmationModalOpen(false); }; - const handleDeleteSuccess = () => { - navigateToUrl(basePath.prepend(paths.observability.slos)); + const handleDeleteConfirm = async () => { + if (slo) { + deleteSlo({ id: slo.id, name: slo.name }); + navigate(basePath.prepend(paths.observability.slos)); + } }; + const navigate = useCallback( + (url: string) => setTimeout(() => navigateToUrl(url)), + [navigateToUrl] + ); + return ( <> {i18n.translate('xpack.observability.slo.sloDetails.headerControl.actions', { defaultMessage: 'Actions', @@ -264,7 +264,7 @@ export function HeaderControl({ isLoading, slo }: Props) { ) : null} diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx index 1dc86a2901a44..6268f3682fa55 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx @@ -6,19 +6,21 @@ */ import React from 'react'; -import { fireEvent, screen } from '@testing-library/react'; +import { fireEvent, screen, waitFor } from '@testing-library/react'; import { useKibana } from '../../utils/kibana_react'; import { useParams } from 'react-router-dom'; import { useLicense } from '../../hooks/use_license'; +import { useCapabilities } from '../../hooks/slo/use_capabilities'; import { useFetchSloDetails } from '../../hooks/slo/use_fetch_slo_details'; +import { useFetchHistoricalSummary } from '../../hooks/slo/use_fetch_historical_summary'; +import { useFetchActiveAlerts } from '../../hooks/slo/use_fetch_active_alerts'; +import { useCloneSlo } from '../../hooks/slo/use_clone_slo'; +import { useDeleteSlo } from '../../hooks/slo/use_delete_slo'; import { render } from '../../utils/test_helper'; import { SloDetailsPage } from './slo_details'; import { buildSlo } from '../../data/slo/slo'; import { paths } from '../../config/paths'; -import { useFetchHistoricalSummary } from '../../hooks/slo/use_fetch_historical_summary'; -import { useCapabilities } from '../../hooks/slo/use_capabilities'; -import { useFetchActiveAlerts } from '../../hooks/slo/use_fetch_active_alerts'; import { HEALTHY_STEP_DOWN_ROLLING_SLO, historicalSummaryData, @@ -34,22 +36,27 @@ jest.mock('react-router-dom', () => ({ jest.mock('../../utils/kibana_react'); jest.mock('../../hooks/use_breadcrumbs'); jest.mock('../../hooks/use_license'); +jest.mock('../../hooks/slo/use_capabilities'); jest.mock('../../hooks/slo/use_fetch_active_alerts'); jest.mock('../../hooks/slo/use_fetch_slo_details'); jest.mock('../../hooks/slo/use_fetch_historical_summary'); -jest.mock('../../hooks/slo/use_capabilities'); +jest.mock('../../hooks/slo/use_clone_slo'); +jest.mock('../../hooks/slo/use_delete_slo'); const useKibanaMock = useKibana as jest.Mock; const useParamsMock = useParams as jest.Mock; const useLicenseMock = useLicense as jest.Mock; +const useCapabilitiesMock = useCapabilities as jest.Mock; const useFetchActiveAlertsMock = useFetchActiveAlerts as jest.Mock; const useFetchSloDetailsMock = useFetchSloDetails as jest.Mock; const useFetchHistoricalSummaryMock = useFetchHistoricalSummary as jest.Mock; -const useCapabilitiesMock = useCapabilities as jest.Mock; +const useCloneSloMock = useCloneSlo as jest.Mock; +const useDeleteSloMock = useDeleteSlo as jest.Mock; const mockNavigate = jest.fn(); -const mockBasePathPrepend = jest.fn(); const mockLocator = jest.fn(); +const mockClone = jest.fn(); +const mockDelete = jest.fn(); const mockKibana = () => { useKibanaMock.mockReturnValue({ @@ -58,12 +65,13 @@ const mockKibana = () => { charts: chartPluginMock.createStartContract(), http: { basePath: { - prepend: mockBasePathPrepend, + prepend: (url: string) => url, }, }, notifications: { toasts: { addSuccess: jest.fn(), + addDanger: jest.fn(), addError: jest.fn(), }, }, @@ -100,6 +108,8 @@ describe('SLO Details Page', () => { sloHistoricalSummaryResponse: historicalSummaryData, }); useFetchActiveAlertsMock.mockReturnValue({ isLoading: false, data: {} }); + useCloneSloMock.mockReturnValue({ mutate: mockClone }); + useDeleteSloMock.mockReturnValue({ mutate: mockDelete }); }); describe('when the incorrect license is found', () => { @@ -111,7 +121,7 @@ describe('SLO Details Page', () => { render(); - expect(mockNavigate).toBeCalledWith(mockBasePathPrepend(paths.observability.slos)); + expect(mockNavigate).toBeCalledWith(paths.observability.slos); }); }); @@ -217,7 +227,26 @@ describe('SLO Details Page', () => { render(); fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton')); - expect(screen.queryByTestId('sloDetailsHeaderControlPopoverClone')).toBeTruthy(); + + const button = screen.queryByTestId('sloDetailsHeaderControlPopoverClone'); + + expect(button).toBeTruthy(); + + fireEvent.click(button!); + + const { id, createdAt, enabled, revision, summary, updatedAt, ...newSlo } = slo; + + expect(mockClone).toBeCalledWith({ + idToCopyFrom: slo.id, + slo: { + ...newSlo, + name: `[Copy] ${newSlo.name}`, + }, + }); + + await waitFor(() => { + expect(mockNavigate).toBeCalledWith(paths.observability.slos); + }); }); it("renders a 'Delete' button under actions menu", async () => { @@ -229,14 +258,25 @@ describe('SLO Details Page', () => { render(); fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton')); - expect(screen.queryByTestId('sloDetailsHeaderControlPopoverDelete')).toBeTruthy(); - const manageRulesButton = screen.queryByTestId('sloDetailsHeaderControlPopoverManageRules'); - expect(manageRulesButton).toBeTruthy(); + const button = screen.queryByTestId('sloDetailsHeaderControlPopoverDelete'); - fireEvent.click(manageRulesButton!); + expect(button).toBeTruthy(); - expect(mockLocator).toBeCalled(); + fireEvent.click(button!); + + const deleteModalConfirmButton = screen.queryByTestId('confirmModalConfirmButton'); + + fireEvent.click(deleteModalConfirmButton!); + + expect(mockDelete).toBeCalledWith({ + id: slo.id, + name: slo.name, + }); + + await waitFor(() => { + expect(mockNavigate).toBeCalledWith(paths.observability.slos); + }); }); it('renders the Overview tab by default', async () => { diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx index 195bc7933a74d..9331c648bbd8c 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useLocation, useHistory } from 'react-router-dom'; import { @@ -51,7 +51,6 @@ export function SloEditForm({ slo }: Props) { const { application: { navigateToUrl }, http: { basePath }, - notifications: { toasts }, triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout }, } = useKibana().services; @@ -121,64 +120,39 @@ export function SloEditForm({ slo }: Props) { const values = getValues(); if (isEditMode) { - try { - const processedValues = transformValuesToUpdateSLOInput(values); + const processedValues = transformValuesToUpdateSLOInput(values); + if (isCreateRuleCheckboxChecked) { await updateSlo({ sloId: slo.id, slo: processedValues }); - - toasts.addSuccess( - i18n.translate('xpack.observability.slo.sloEdit.update.success', { - defaultMessage: 'Successfully updated {name}', - values: { name: getValues().name }, - }) + navigate( + basePath.prepend( + `${paths.observability.sloEdit(slo.id)}?${CREATE_RULE_SEARCH_PARAM}=true` + ) ); - - if (isCreateRuleCheckboxChecked) { - navigateToUrl( - basePath.prepend( - `${paths.observability.sloEdit(slo.id)}?${CREATE_RULE_SEARCH_PARAM}=true` - ) - ); - } else { - navigateToUrl(basePath.prepend(paths.observability.slos)); - } - } catch (error) { - toasts.addError(new Error(error), { - title: i18n.translate('xpack.observability.slo.sloEdit.creation.error', { - defaultMessage: 'Something went wrong', - }), - }); + } else { + updateSlo({ sloId: slo.id, slo: processedValues }); + navigate(basePath.prepend(paths.observability.slos)); } } else { - try { - const processedValues = transformValuesToCreateSLOInput(values); + const processedValues = transformValuesToCreateSLOInput(values); + if (isCreateRuleCheckboxChecked) { const { id } = await createSlo({ slo: processedValues }); - - toasts.addSuccess( - i18n.translate('xpack.observability.slo.sloEdit.creation.success', { - defaultMessage: 'Successfully created {name}', - values: { name: getValues().name }, - }) + navigate( + basePath.prepend(`${paths.observability.sloEdit(id)}?${CREATE_RULE_SEARCH_PARAM}=true`) ); - - if (isCreateRuleCheckboxChecked) { - navigateToUrl( - basePath.prepend(`${paths.observability.sloEdit(id)}?${CREATE_RULE_SEARCH_PARAM}=true`) - ); - } else { - navigateToUrl(basePath.prepend(paths.observability.slos)); - } - } catch (error) { - toasts.addError(new Error(error), { - title: i18n.translate('xpack.observability.slo.sloEdit.creation.error', { - defaultMessage: 'Something went wrong', - }), - }); + } else { + createSlo({ slo: processedValues }); + navigate(basePath.prepend(paths.observability.slos)); } } }; + const navigate = useCallback( + (url: string) => setTimeout(() => navigateToUrl(url)), + [navigateToUrl] + ); + const handleChangeCheckbox = () => { setIsCreateRuleCheckboxChecked(!isCreateRuleCheckboxChecked); }; diff --git a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx index 73b386e54ca16..da327bc22b492 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx @@ -539,46 +539,6 @@ describe('SLO Edit Page', () => { }); describe('when submitting has completed successfully', () => { - it('renders a success toast', async () => { - const slo = buildSlo(); - - jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '123' }); - jest - .spyOn(Router, 'useLocation') - .mockReturnValue({ pathname: 'foo', search: '', state: '', hash: '' }); - - useFetchSloMock.mockReturnValue({ isLoading: false, slo }); - - useFetchIndicesMock.mockReturnValue({ - isLoading: false, - indices: [{ name: 'some-index' }], - }); - - useCreateSloMock.mockReturnValue({ - mutateAsync: jest.fn().mockResolvedValue('success'), - isLoading: false, - isSuccess: false, - isError: false, - }); - - useUpdateSloMock.mockReturnValue({ - mutateAsync: jest.fn().mockResolvedValue('success'), - isLoading: false, - isSuccess: false, - isError: false, - }); - - render(); - - expect(screen.queryByTestId('sloFormSubmitButton')).toBeEnabled(); - - await waitFor(() => { - fireEvent.click(screen.getByTestId('sloFormSubmitButton')); - }); - - expect(mockAddSuccess).toBeCalled(); - }); - it('navigates to the SLO List page when checkbox to create new rule is not checked', async () => { const slo = buildSlo(); @@ -615,8 +575,9 @@ describe('SLO Edit Page', () => { await waitFor(() => { fireEvent.click(screen.getByTestId('sloFormSubmitButton')); }); - - expect(mockNavigate).toBeCalledWith(mockBasePathPrepend(paths.observability.slos)); + await waitFor(() => { + expect(mockNavigate).toBeCalledWith(mockBasePathPrepend(paths.observability.slos)); + }); }); it('navigates to the SLO Edit page when checkbox to create new rule is checked', async () => { @@ -657,9 +618,11 @@ describe('SLO Edit Page', () => { fireEvent.click(screen.getByTestId('sloFormSubmitButton')); }); - expect(mockNavigate).toBeCalledWith( - mockBasePathPrepend(`${paths.observability.sloEdit(slo.id)}?create-rule=true`) - ); + await waitFor(() => { + expect(mockNavigate).toBeCalledWith( + mockBasePathPrepend(`${paths.observability.sloEdit(slo.id)}?create-rule=true`) + ); + }); }); it('opens the Add Rule Flyout when visiting an existing SLO with search params set', async () => { @@ -698,47 +661,5 @@ describe('SLO Edit Page', () => { }); }); }); - - describe('when submitting has not completed successfully', () => { - it('renders an error toast', async () => { - const slo = buildSlo(); - - jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '123' }); - jest - .spyOn(Router, 'useLocation') - .mockReturnValue({ pathname: 'foo', search: '', state: '', hash: '' }); - - useFetchSloMock.mockReturnValue({ isLoading: false, slo }); - - useFetchIndicesMock.mockReturnValue({ - isLoading: false, - indices: [{ name: 'some-index' }], - }); - - useCreateSloMock.mockReturnValue({ - mutateAsync: jest.fn().mockRejectedValue('argh, I died'), - isLoading: false, - isSuccess: false, - isError: false, - }); - - useUpdateSloMock.mockReturnValue({ - mutateAsync: jest.fn().mockRejectedValue('argh, I died'), - isLoading: false, - isSuccess: false, - isError: false, - }); - - render(); - - expect(screen.queryByTestId('sloFormSubmitButton')).toBeEnabled(); - - await waitFor(() => { - fireEvent.click(screen.getByTestId('sloFormSubmitButton')); - }); - - expect(mockAddError).toBeCalled(); - }); - }); }); }); diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx index 89d5eadf4d9ad..380d1a898bf0c 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexGroup, EuiSkeletonRectangle } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; @@ -14,25 +14,54 @@ import { SloIndicatorTypeBadge } from './slo_indicator_type_badge'; import { SloStatusBadge } from '../../../../components/slo/slo_status_badge'; import { SloActiveAlertsBadge } from '../../../../components/slo/slo_status_badge/slo_active_alerts_badge'; import { SloTimeWindowBadge } from './slo_time_window_badge'; +import { SloRulesBadge } from './slo_rules_badge'; import type { ActiveAlerts } from '../../../../hooks/slo/use_fetch_active_alerts'; import type { SloRule } from '../../../../hooks/slo/use_fetch_rules_for_slo'; -import { SloRulesBadge } from './slo_rules_badge'; export interface Props { activeAlerts?: ActiveAlerts; + isLoading: boolean; rules: Array> | undefined; slo: SLOWithSummaryResponse; onClickRuleBadge: () => void; } -export function SloBadges({ activeAlerts, rules, slo, onClickRuleBadge }: Props) { +export function SloBadges({ activeAlerts, isLoading, rules, slo, onClickRuleBadge }: Props) { return ( - - - - - + {isLoading ? ( + <> + + + + + ) : ( + <> + + + + + + + )} ); } diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx index b1b2d341a9bd8..e132661aa16ad 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx @@ -5,94 +5,49 @@ * 2.0. */ +import React from 'react'; import { EuiConfirmModal } from '@elastic/eui'; -import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import type { SLOWithSummaryResponse } from '@kbn/slo-schema'; -import { useKibana } from '../../../utils/kibana_react'; -import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo'; export interface SloDeleteConfirmationModalProps { slo: SLOWithSummaryResponse; onCancel: () => void; - onSuccess?: () => void; + onConfirm: () => void; } export function SloDeleteConfirmationModal({ - slo: { id, name }, + slo: { name }, onCancel, - onSuccess, + onConfirm, }: SloDeleteConfirmationModalProps) { - const { - notifications: { toasts }, - } = useKibana().services; - - const [isVisible, setIsVisible] = useState(true); - - const { mutate: deleteSlo, isSuccess, isError } = useDeleteSlo(id); - - if (isSuccess) { - toasts.addSuccess(getDeleteSuccesfulMessage(name)); - onSuccess?.(); - } - - if (isError) { - toasts.addDanger(getDeleteFailMessage(name)); - } - - const handleConfirm = () => { - setIsVisible(false); - deleteSlo({ id }); - }; - - return isVisible ? ( + return ( {i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.descriptionText', { defaultMessage: "You can't recover {name} after deleting.", values: { name }, })} - ) : null; -} - -const getTitle = () => - i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.title', { - defaultMessage: 'Are you sure?', - }); - -const getCancelButtonText = () => - i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.cancelButtonLabel', { - defaultMessage: 'Cancel', - }); - -const getConfirmButtonText = (name: string) => - i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.deleteButtonLabel', { - defaultMessage: 'Delete {name}', - values: { name }, - }); - -const getDeleteSuccesfulMessage = (name: string) => - i18n.translate( - 'xpack.observability.slo.slo.deleteConfirmationModal.successNotification.descriptionText', - { - defaultMessage: 'Deleted {name}', - values: { name }, - } - ); - -const getDeleteFailMessage = (name: string) => - i18n.translate( - 'xpack.observability.slo.slo.deleteConfirmationModal.errorNotification.descriptionText', - { - defaultMessage: 'Failed to delete {name}', - values: { name }, - } ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx index e71a2cd98a0e6..0efab27b0ebdf 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx @@ -29,7 +29,7 @@ export function SloList({ autoRefresh }: Props) { const [sort, setSort] = useState('creationTime'); const [indicatorTypeFilter, setIndicatorTypeFilter] = useState([]); - const { isLoading, isError, sloList, refetch } = useFetchSloList({ + const { isInitialLoading, isLoading, isRefetching, isError, sloList, refetch } = useFetchSloList({ page: activePage + 1, name: query, sortBy: sort, @@ -69,7 +69,15 @@ export function SloList({ autoRefresh }: Props) { - + {results.length ? ( diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx index 3fe0f5d31f543..eb3a29ea61958 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx @@ -6,7 +6,7 @@ */ import React, { useState } from 'react'; -import { useIsMutating, useQueryClient } from '@tanstack/react-query'; +import { useQueryClient } from '@tanstack/react-query'; import { EuiButtonIcon, EuiContextMenuItem, @@ -46,6 +46,7 @@ export interface SloListItemProps { historicalSummary?: HistoricalSummaryResponse[]; historicalSummaryLoading: boolean; activeAlerts?: ActiveAlerts; + onConfirmDelete: (slo: SLOWithSummaryResponse) => void; } export function SloListItem({ @@ -54,6 +55,7 @@ export function SloListItem({ historicalSummary = [], historicalSummaryLoading, activeAlerts, + onConfirmDelete, }: SloListItemProps) { const { application: { navigateToUrl }, @@ -69,7 +71,6 @@ export function SloListItem({ const filteredRuleTypes = useGetFilteredRuleTypes(); const { mutate: cloneSlo } = useCloneSlo(); - const isDeletingSlo = Boolean(useIsMutating(['deleteSlo', slo.id])); const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); const [isAddRuleFlyoutOpen, setIsAddRuleFlyoutOpen] = useState(false); @@ -123,21 +124,17 @@ export function SloListItem({ setIsActionsPopoverOpen(false); }; + const handleDeleteConfirm = () => { + setDeleteConfirmationModalOpen(false); + onConfirmDelete(slo); + }; + const handleDeleteCancel = () => { setDeleteConfirmationModalOpen(false); }; return ( - + {/* CONTENT */} @@ -146,13 +143,18 @@ export function SloListItem({ - - {slo.name} - + {slo.summary ? ( + + {slo.name} + + ) : ( + {slo.name} + )} - + {slo.summary ? ( + + ) : null} @@ -276,7 +280,11 @@ export function SloListItem({ ) : null} {isDeleteConfirmationModalOpen ? ( - + ) : null} ); diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx index 2f6f71612788e..e70f8139fe9f5 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx @@ -11,6 +11,7 @@ import type { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; import { useFetchRulesForSlo } from '../../../hooks/slo/use_fetch_rules_for_slo'; import { useFetchHistoricalSummary } from '../../../hooks/slo/use_fetch_historical_summary'; +import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo'; import { SloListItem } from './slo_list_item'; import { SloListEmpty } from './slo_list_empty'; import { SloListError } from './slo_list_error'; @@ -29,6 +30,8 @@ export function SloListItems({ sloList, loading, error }: Props) { const { isLoading: historicalSummaryLoading, sloHistoricalSummaryResponse } = useFetchHistoricalSummary({ sloIds }); + const { mutate: deleteSlo } = useDeleteSlo(); + if (!loading && !error && sloList.length === 0) { return ; } @@ -36,6 +39,10 @@ export function SloListItems({ sloList, loading, error }: Props) { return ; } + const handleDelete = (slo: SLOWithSummaryResponse) => { + deleteSlo({ id: slo.id, name: slo.name }); + }; + return ( {sloList.map((slo) => ( @@ -46,6 +53,7 @@ export function SloListItems({ sloList, loading, error }: Props) { historicalSummary={sloHistoricalSummaryResponse?.[slo.id]} historicalSummaryLoading={historicalSummaryLoading} slo={slo} + onConfirmDelete={handleDelete} /> ))} diff --git a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx index 12a6f2762de58..f017f8690305f 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { screen, act } from '@testing-library/react'; +import { screen, act, waitFor } from '@testing-library/react'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; @@ -23,6 +23,7 @@ import { SlosPage } from './slos'; import { emptySloList, sloList } from '../../data/slo/slo'; import { historicalSummaryData } from '../../data/slo/historical_summary_data'; import { useCapabilities } from '../../hooks/slo/use_capabilities'; +import { paths } from '../../config/paths'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -70,7 +71,7 @@ const mockKibana = () => { charts: chartPluginMock.createSetupContract(), http: { basePath: { - prepend: jest.fn(), + prepend: (url: string) => url, }, }, notifications: { @@ -106,17 +107,22 @@ describe('SLOs Page', () => { }); describe('when the incorrect license is found', () => { - it('renders the welcome prompt with subscription buttons', async () => { + beforeEach(() => { useFetchSloListMock.mockReturnValue({ isLoading: false, sloList: emptySloList }); useLicenseMock.mockReturnValue({ hasAtLeast: () => false }); - + useFetchHistoricalSummaryMock.mockReturnValue({ + isLoading: false, + sloHistoricalSummaryResponse: {}, + }); + }); + it('navigates to the SLOs Welcome Page', async () => { await act(async () => { render(); }); - expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy(); - expect(screen.queryByTestId('slosPageWelcomePromptSignupForCloudButton')).toBeTruthy(); - expect(screen.queryByTestId('slosPageWelcomePromptSignupForLicenseButton')).toBeTruthy(); + await waitFor(() => { + expect(mockNavigate).toBeCalledWith(paths.observability.slosWelcome); + }); }); }); @@ -125,14 +131,20 @@ describe('SLOs Page', () => { useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); }); - it('renders the SLOs Welcome Prompt when the API has finished loading and there are no results', async () => { + it('navigates to the SLOs Welcome Page when the API has finished loading and there are no results', async () => { useFetchSloListMock.mockReturnValue({ isLoading: false, sloList: emptySloList }); + useFetchHistoricalSummaryMock.mockReturnValue({ + isLoading: false, + sloHistoricalSummaryResponse: {}, + }); await act(async () => { render(); }); - expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy(); + await waitFor(() => { + expect(mockNavigate).toBeCalledWith(paths.observability.slosWelcome); + }); }); it('should have a create new SLO button', async () => { @@ -206,7 +218,9 @@ describe('SLOs Page', () => { button.click(); - expect(mockNavigate).toBeCalled(); + expect(mockNavigate).toBeCalledWith( + `${paths.observability.sloEdit(sloList.results.at(0)?.id || '')}` + ); }); it('allows creating a new rule for an SLO', async () => { @@ -283,7 +297,10 @@ describe('SLOs Page', () => { screen.getByTestId('confirmModalConfirmButton').click(); - expect(mockDeleteSlo).toBeCalledWith({ id: sloList.results.at(0)?.id }); + expect(mockDeleteSlo).toBeCalledWith({ + id: sloList.results.at(0)?.id, + name: sloList.results.at(0)?.name, + }); }); it('allows cloning an SLO', async () => { diff --git a/x-pack/plugins/observability/public/pages/slos/slos.tsx b/x-pack/plugins/observability/public/pages/slos/slos.tsx index f9cf30cb7b344..9b7df97e1e13c 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -16,7 +16,6 @@ import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; import { useCapabilities } from '../../hooks/slo/use_capabilities'; import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list'; import { SloList } from './components/slo_list'; -import { SloListWelcomePrompt } from './components/slo_list_welcome_prompt'; import { AutoRefreshButton } from './components/auto_refresh_button'; import { HeaderTitle } from './components/header_title'; import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button'; @@ -55,14 +54,16 @@ export function SlosPage() { setIsAutoRefreshing(!isAutoRefreshing); }; + useEffect(() => { + if ((!isLoading && total === 0) || hasAtLeast('platinum') === false) { + navigateToUrl(basePath.prepend(paths.observability.slosWelcome)); + } + }, [basePath, hasAtLeast, isLoading, navigateToUrl, total]); + if (isInitialLoading) { return null; } - if ((!isLoading && total === 0) || !hasAtLeast('platinum')) { - return ; - } - return ( { + useKibanaMock.mockReturnValue({ + services: { + application: { navigateToUrl: mockNavigate }, + http: { + basePath: { + prepend: (url: string) => url, + }, + }, + }, + }); +}; + +describe('SLOs Welcome Page', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockKibana(); + useCapabilitiesMock.mockReturnValue({ hasWriteCapabilities: true, hasReadCapabilities: true }); + }); + + describe('when the incorrect license is found', () => { + it('renders the welcome message with subscription buttons', async () => { + useFetchSloListMock.mockReturnValue({ isLoading: false, sloList: emptySloList }); + useLicenseMock.mockReturnValue({ hasAtLeast: () => false }); + + render(); + + expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy(); + expect(screen.queryByTestId('slosPageWelcomePromptSignupForCloudButton')).toBeTruthy(); + expect(screen.queryByTestId('slosPageWelcomePromptSignupForLicenseButton')).toBeTruthy(); + }); + }); + + describe('when the correct license is found', () => { + beforeEach(() => { + useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); + }); + + describe('when loading is done and no results are found', () => { + beforeEach(() => { + useFetchSloListMock.mockReturnValue({ isLoading: false, emptySloList }); + }); + + it('should display the welcome message with a Create new SLO button which should navigate to the SLO Creation page', async () => { + render(); + expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy(); + + const createNewSloButton = screen.queryByTestId('o11ySloListWelcomePromptCreateSloButton'); + expect(createNewSloButton).toBeTruthy(); + createNewSloButton?.click(); + + await waitFor(() => { + expect(mockNavigate).toBeCalledWith(paths.observability.sloCreate); + }); + }); + }); + + describe('when loading is done and results are found', () => { + beforeEach(() => { + useFetchSloListMock.mockReturnValue({ isLoading: false, sloList }); + }); + + it('should navigate to the SLO List page', async () => { + render(); + await waitFor(() => { + expect(mockNavigate).toBeCalledWith(paths.observability.slos); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx b/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.tsx similarity index 88% rename from x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx rename to x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.tsx index 478c0d85cc613..c7373efc2cfbf 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx +++ b/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { EuiPageTemplate, EuiButton, @@ -18,13 +18,14 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useKibana } from '../../../utils/kibana_react'; -import { useLicense } from '../../../hooks/use_license'; -import { usePluginContext } from '../../../hooks/use_plugin_context'; -import { paths } from '../../../config/paths'; +import { useKibana } from '../../utils/kibana_react'; +import { useLicense } from '../../hooks/use_license'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list'; +import { paths } from '../../config/paths'; import illustration from './assets/illustration.svg'; -export function SloListWelcomePrompt() { +export function SlosWelcomePage() { const { application: { navigateToUrl }, http: { basePath }, @@ -32,14 +33,24 @@ export function SloListWelcomePrompt() { const { ObservabilityPageTemplate } = usePluginContext(); const { hasAtLeast } = useLicense(); - const hasRightLicense = hasAtLeast('platinum'); + const { isLoading, sloList } = useFetchSloList(); + const { total } = sloList || { total: 0 }; + const handleClickCreateSlo = () => { navigateToUrl(basePath.prepend(paths.observability.sloCreate)); }; - return ( + const hasSlosAndHasPermissions = total > 0 && hasAtLeast('platinum') === true; + + useEffect(() => { + if (hasSlosAndHasPermissions) { + navigateToUrl(basePath.prepend(paths.observability.slos)); + } + }, [basePath, hasSlosAndHasPermissions, navigateToUrl]); + + return hasSlosAndHasPermissions || isLoading ? null : ( { + return ; + }, + params: {}, + exact: true, + }, '/slos/edit/:sloId': { handler: () => { return ; From ece68051e142737e3b946c40cda0bb67ea604db7 Mon Sep 17 00:00:00 2001 From: Ashokaditya <1849116+ashokaditya@users.noreply.github.com> Date: Thu, 27 Apr 2023 19:40:03 +0200 Subject: [PATCH 15/19] [Fleet][Message Signing] Rotate key pair error handling (#155864) ## Summary Adds more info to key rotation API response in case of errors. Follow up of elastic/kibana/pull/155252 ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/common/errors.ts | 2 + .../message_signing_service/handlers.test.ts | 37 +++++----- .../message_signing_service/handlers.ts | 26 ++++--- .../security/message_signing_service.test.ts | 58 ++++++++++++++- .../security/message_signing_service.ts | 72 +++++++++++-------- 5 files changed, 132 insertions(+), 63 deletions(-) diff --git a/x-pack/plugins/fleet/common/errors.ts b/x-pack/plugins/fleet/common/errors.ts index 8e22b971be6ad..75c789e30e9ce 100644 --- a/x-pack/plugins/fleet/common/errors.ts +++ b/x-pack/plugins/fleet/common/errors.ts @@ -17,3 +17,5 @@ export class FleetError extends Error { } export class PackagePolicyValidationError extends FleetError {} + +export class MessageSigningError extends FleetError {} diff --git a/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.test.ts b/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.test.ts index b20a29121f81b..37fac077af7ac 100644 --- a/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.test.ts @@ -10,7 +10,7 @@ import { httpServerMock, coreMock } from '@kbn/core/server/mocks'; import type { KibanaRequest } from '@kbn/core/server'; import { createAppContextStartContractMock, xpackMocks } from '../../mocks'; -import { appContextService } from '../../services/app_context'; +import { appContextService } from '../../services'; import type { FleetRequestHandlerContext } from '../../types'; import { rotateKeyPairHandler } from './handlers'; @@ -44,44 +44,42 @@ describe('FleetMessageSigningServiceHandler', () => { appContextService.stop(); }); - it('POST /message_signing_service/rotate_key_pair?acknowledge=true succeeds with an 200 with `acknowledge=true`', async () => { - (appContextService.getMessageSigningService()?.rotateKeyPair as jest.Mock).mockReturnValue( - true - ); + it(`POST /message_signing_service/rotate_key_pair?acknowledge=true fails with an 500 with "acknowledge=true" when no messaging service`, async () => { + appContextService.start({ + ...createAppContextStartContractMock(), + // @ts-expect-error + messageSigningService: undefined, + }); await rotateKeyPairHandler( coreMock.createCustomRequestHandlerContext(context), request, response ); - expect(response.ok).toHaveBeenCalledWith({ + expect(response.customError).toHaveBeenCalledWith({ + statusCode: 500, body: { - message: 'Key pair rotated successfully.', + message: 'Failed to rotate key pair. Message signing service is unavailable!', }, }); }); - it(`POST /message_signing_service/rotate_key_pair?acknowledge=true fails with an 500 with "acknowledge=true" when rotateKeyPair doesn't succeed`, async () => { - (appContextService.getMessageSigningService()?.rotateKeyPair as jest.Mock).mockReturnValue( - false - ); - + it('POST /message_signing_service/rotate_key_pair?acknowledge=true succeeds with `acknowledge=true`', async () => { await rotateKeyPairHandler( coreMock.createCustomRequestHandlerContext(context), request, response ); - expect(response.customError).toHaveBeenCalledWith({ - statusCode: 500, + expect(response.ok).toHaveBeenCalledWith({ body: { - message: 'Failed to rotate key pair!', + message: 'Key pair rotated successfully.', }, }); }); - it(`POST /message_signing_service/rotate_key_pair?acknowledge=true fails with an 500 with "acknowledge=true" when no messaging service`, async () => { - (appContextService.getMessageSigningService()?.rotateKeyPair as jest.Mock).mockReturnValue( - undefined + it('POST /message_signing_service/rotate_key_pair?acknowledge=true throws errors`', async () => { + (appContextService.getMessageSigningService()?.rotateKeyPair as jest.Mock).mockRejectedValue( + Error('foo') ); await rotateKeyPairHandler( @@ -89,10 +87,11 @@ describe('FleetMessageSigningServiceHandler', () => { request, response ); + expect(response.customError).toHaveBeenCalledWith({ statusCode: 500, body: { - message: 'Failed to rotate key pair!', + message: 'foo', }, }); }); diff --git a/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.ts b/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.ts index 18a00ca86fdd8..b7d63f95a25f2 100644 --- a/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.ts @@ -18,25 +18,29 @@ export const rotateKeyPairHandler: FleetRequestHandler< TypeOf, undefined > = async (_, __, response) => { + const logger = appContextService.getLogger(); + const messageSigningService = appContextService.getMessageSigningService(); + if (!messageSigningService) { + const errorMessage = 'Failed to rotate key pair. Message signing service is unavailable!'; + logger.error(errorMessage); + return response.customError({ + statusCode: 500, + body: { + message: errorMessage, + }, + }); + } + try { - const rotateKeyPairResponse = await appContextService - .getMessageSigningService() - ?.rotateKeyPair(); + await messageSigningService.rotateKeyPair(); - if (!rotateKeyPairResponse) { - return response.customError({ - statusCode: 500, - body: { - message: 'Failed to rotate key pair!', - }, - }); - } return response.ok({ body: { message: 'Key pair rotated successfully.', }, }); } catch (error) { + logger.error(error.meta); return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts b/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts index 6503f2f08d566..cb4288cd31488 100644 --- a/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts +++ b/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts @@ -14,7 +14,7 @@ import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/s import { MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE } from '../../constants'; import { createAppContextStartContractMock } from '../../mocks'; -import { appContextService } from '../app_context'; +import { appContextService } from '..'; import { type MessageSigningServiceInterface, @@ -108,7 +108,7 @@ describe('MessageSigningService', () => { it('can correctly rotate existing key pair', async () => { mockCreatePointInTimeFinderAsInternalUserOnce([keyPairObj]); - const rotateKeyPairResponse = await messageSigningService.rotateKeyPair(); + await messageSigningService.rotateKeyPair(); expect(soClientMock.delete).toBeCalledWith( MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, @@ -119,8 +119,60 @@ describe('MessageSigningService', () => { public_key: expect.any(String), passphrase: expect.any(String), }); + }); + + it('does not generate key pair on rotation if no key pair exists', async () => { + // no previous key pair + mockCreatePointInTimeFinderAsInternalUserOnce([]); + + const response = messageSigningService.rotateKeyPair(); + await expect(response).rejects.toThrowError( + 'Error rotating key pair: Error fetching current key pair: No current key pair found!' + ); + }); + + it('throws `getCurrentKeyPairObj` error if any on rotate', async () => { + mockCreatePointInTimeFinderAsInternalUserOnce([keyPairObj]); + // mock delete to throw + jest + .spyOn(messageSigningService, 'getCurrentKeyPairObj' as any) + .mockRejectedValue(Error('foo')); + + const response = messageSigningService.rotateKeyPair(); + await expect(response).rejects.toThrowError( + 'Error rotating key pair: Error fetching current key pair: foo' + ); + }); + + it('throws `soClient` `delete` error if any on rotate', async () => { + mockCreatePointInTimeFinderAsInternalUserOnce([keyPairObj]); + // mock delete to throw + soClientMock.delete.mockRejectedValue(Error('foo')); + + const response = messageSigningService.rotateKeyPair(); + await expect(response).rejects.toThrowError( + 'Error rotating key pair: Error deleting current key pair: foo' + ); + }); + + it('throws `soClient` `create` error if any on rotate', async () => { + mockCreatePointInTimeFinderAsInternalUserOnce([keyPairObj]); + // mock delete to throw + soClientMock.create.mockRejectedValue(Error('foo')); + + const response = messageSigningService.rotateKeyPair(); + await expect(response).rejects.toThrowError( + 'Error rotating key pair: Error creating key pair: foo' + ); + }); + + it('throws `generateKeyPair` error if any on rotate', async () => { + mockCreatePointInTimeFinderAsInternalUserOnce([keyPairObj]); + // mock delete to throw + messageSigningService.generateKeyPair = jest.fn().mockRejectedValue(Error('foo')); - expect(rotateKeyPairResponse).toEqual(true); + const response = messageSigningService.rotateKeyPair(); + await expect(response).rejects.toThrowError('Error rotating key pair: foo'); }); it('does not generate key pair if one exists', async () => { diff --git a/x-pack/plugins/fleet/server/services/security/message_signing_service.ts b/x-pack/plugins/fleet/server/services/security/message_signing_service.ts index fc5583dec01d0..6fcf92c0aebe8 100644 --- a/x-pack/plugins/fleet/server/services/security/message_signing_service.ts +++ b/x-pack/plugins/fleet/server/services/security/message_signing_service.ts @@ -15,6 +15,8 @@ import type { import type { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; +import { MessageSigningError } from '../../../common/errors'; + import { MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE } from '../../constants'; import { appContextService } from '../app_context'; @@ -30,7 +32,7 @@ export interface MessageSigningServiceInterface { generateKeyPair( providedPassphrase?: string ): Promise<{ privateKey: string; publicKey: string; passphrase: string }>; - rotateKeyPair(): Promise; + rotateKeyPair(): Promise; sign(message: Buffer | Record): Promise<{ data: Buffer; signature: string }>; getPublicKey(): Promise; } @@ -40,15 +42,13 @@ export class MessageSigningService implements MessageSigningServiceInterface { constructor(private esoClient: EncryptedSavedObjectsClient) {} - public get isEncryptionAvailable(): boolean { + public get isEncryptionAvailable(): MessageSigningServiceInterface['isEncryptionAvailable'] { return appContextService.getEncryptedSavedObjectsSetup()?.canEncrypt ?? false; } - public async generateKeyPair(providedPassphrase?: string): Promise<{ - privateKey: string; - publicKey: string; - passphrase: string; - }> { + public async generateKeyPair( + providedPassphrase?: string + ): ReturnType { const existingKeyPair = await this.checkForExistingKeyPair(); if (existingKeyPair) { return existingKeyPair; @@ -82,21 +82,25 @@ export class MessageSigningService implements MessageSigningServiceInterface { } : { ...keypairSoObject, passphrase_plain: passphrase }; - await this.soClient.create>( - MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, - keypairSoObject - ); + try { + await this.soClient.create>( + MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, + keypairSoObject + ); - return { - privateKey, - publicKey, - passphrase, - }; + return { + privateKey, + publicKey, + passphrase, + }; + } catch (error) { + throw new MessageSigningError(`Error creating key pair: ${error.message}`, error); + } } public async sign( message: Buffer | Record - ): Promise<{ data: Buffer; signature: string }> { + ): ReturnType { const { privateKey: serializedPrivateKey, passphrase } = await this.generateKeyPair(); const msgBuffer = Buffer.isBuffer(message) @@ -125,7 +129,7 @@ export class MessageSigningService implements MessageSigningServiceInterface { }; } - public async getPublicKey(): Promise { + public async getPublicKey(): ReturnType { const { publicKey } = await this.generateKeyPair(); if (!publicKey) { @@ -135,26 +139,34 @@ export class MessageSigningService implements MessageSigningServiceInterface { return publicKey; } - public async rotateKeyPair(): Promise { - const isRemoved = await this.removeKeyPair(); - if (isRemoved) { + public async rotateKeyPair(): ReturnType { + try { + await this.removeKeyPair(); await this.generateKeyPair(); - return true; + } catch (error) { + throw new MessageSigningError(`Error rotating key pair: ${error.message}`, error); } - return false; - // TODO: Apply changes to all policies } - private async removeKeyPair(): Promise { - const currentKeyPair = await this.getCurrentKeyPairObj(); - if (currentKeyPair) { + private async removeKeyPair(): Promise { + let currentKeyPair: Awaited>; + try { + currentKeyPair = await this.getCurrentKeyPairObj(); + if (!currentKeyPair) { + throw new MessageSigningError('No current key pair found!'); + } + } catch (error) { + throw new MessageSigningError(`Error fetching current key pair: ${error.message}`, error); + } + + try { await this.soClient.delete(MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, currentKeyPair.id); - return true; + } catch (error) { + throw new MessageSigningError(`Error deleting current key pair: ${error.message}`, error); } - return false; } - private get soClient() { + private get soClient(): SavedObjectsClientContract { if (this._soClient) { return this._soClient; } From 096d003c2c16cbda566fe149e0f4db58be681b3d Mon Sep 17 00:00:00 2001 From: Karl Godard Date: Thu, 27 Apr 2023 10:42:27 -0700 Subject: [PATCH 16/19] [D4C] Response with no actions will now show an error + fix to null pointer when actions deleted in yaml editor. (#155952) ## Summary Fixes an issue where the user could deselect all actions in a response (which should not be allowed). Also fixes an issue in the yaml editor if you delete all actions (causing a null pointer exception). ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --- .../control_general_view/translations.ts | 4 ++ .../index.test.tsx | 15 +++++ .../control_general_view_response/index.tsx | 61 +++++++++++++------ .../components/control_yaml_view/index.tsx | 6 +- 4 files changed, 65 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/cloud_defend/public/components/control_general_view/translations.ts b/x-pack/plugins/cloud_defend/public/components/control_general_view/translations.ts index d80ef8030ea6a..1cb3d92150b6b 100644 --- a/x-pack/plugins/cloud_defend/public/components/control_general_view/translations.ts +++ b/x-pack/plugins/cloud_defend/public/components/control_general_view/translations.ts @@ -156,6 +156,10 @@ export const errorValueRequired = i18n.translate('xpack.cloudDefend.errorValueRe defaultMessage: 'At least one value is required.', }); +export const errorActionRequired = i18n.translate('xpack.cloudDefend.errorActionRequired', { + defaultMessage: 'At least one action is required.', +}); + export const getSelectorIconTooltip = (type: SelectorType) => { switch (type) { case 'process': diff --git a/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.test.tsx b/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.test.tsx index 24bfcbadc124f..099ce4a1c2a2e 100644 --- a/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.test.tsx +++ b/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.test.tsx @@ -206,4 +206,19 @@ describe('', () => { expect(onDuplicate.mock.calls).toHaveLength(1); expect(onDuplicate.mock.calls[0][0]).toEqual(mockResponse); }); + + it('shows an error if no actions specified', async () => { + const { getByTestId, getByText, rerender } = render(); + + const checkBox = getByTestId('cloud-defend-chkalertaction'); + if (checkBox) { + userEvent.click(checkBox); + } + + const updatedResponse = onChange.mock.calls[0][0]; + rerender(); + + expect(getByText(i18n.errorActionRequired)).toBeTruthy(); + expect(updatedResponse.hasErrors).toBeTruthy(); + }); }); diff --git a/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.tsx b/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.tsx index 88fed27c61455..af037c70bd71a 100644 --- a/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.tsx +++ b/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.tsx @@ -29,7 +29,12 @@ import { } from '@elastic/eui'; import { useStyles } from './styles'; import { useStyles as useSelectorStyles } from '../control_general_view_selector/styles'; -import { ControlGeneralViewResponseDeps, ResponseAction } from '../../types'; +import { + ControlGeneralViewResponseDeps, + ResponseAction, + Response, + ControlFormErrorMap, +} from '../../types'; import * as i18n from '../control_general_view/translations'; import { getSelectorTypeIcon } from '../../common/utils'; @@ -59,6 +64,21 @@ export const ControlGeneralViewResponse = ({ const [accordionState, setAccordionState] = useState<'open' | 'closed'>( responses.length - 1 === index ? 'open' : 'closed' ); + const onResponseChange = useCallback( + (resp: Response, i: number) => { + const hasMatch = resp.match.length > 0; + const hasActions = resp.actions.length > 0; + + if (!hasMatch || !hasActions) { + resp.hasErrors = true; + } else { + delete resp.hasErrors; + } + + onChange(resp, i); + }, + [onChange] + ); const onTogglePopover = useCallback(() => { setPopoverOpen(!isPopoverOpen); @@ -81,15 +101,10 @@ export const ControlGeneralViewResponse = ({ const onChangeMatches = useCallback( (options) => { response.match = options.map((option: EuiComboBoxOptionOption) => option.value); - if (response.match.length === 0) { - response.hasErrors = true; - } else { - delete response.hasErrors; // keeps it out of the yaml. - } - onChange(response, index); + onResponseChange(response, index); }, - [index, onChange, response] + [index, onResponseChange, response] ); const onChangeExcludes = useCallback( @@ -100,9 +115,9 @@ export const ControlGeneralViewResponse = ({ delete response.exclude; } - onChange(response, index); + onResponseChange(response, index); }, - [index, onChange, response] + [index, onResponseChange, response] ); const selectorOptions = useMemo(() => { @@ -142,8 +157,8 @@ export const ControlGeneralViewResponse = ({ const onShowExclude = useCallback(() => { const updatedResponse = { ...response }; updatedResponse.exclude = []; - onChange(updatedResponse, index); - }, [index, onChange, response]); + onResponseChange(updatedResponse, index); + }, [index, onResponseChange, response]); const logSelected = response.actions.includes('log'); const alertSelected = response.actions.includes('alert'); @@ -170,20 +185,24 @@ export const ControlGeneralViewResponse = ({ updatedResponse.actions.splice(actionIndex, 1); } - onChange(updatedResponse, index); + onResponseChange(updatedResponse, index); }, - [index, onChange, response] + [index, onResponseChange, response] ); const errors = useMemo(() => { - const errs: string[] = []; + const errs: ControlFormErrorMap = {}; if (response.match.length === 0) { - errs.push(i18n.errorValueRequired); + errs.match = [i18n.errorValueRequired]; + } + + if (response.actions.length === 0) { + errs.actions = [i18n.errorActionRequired]; } return errs; - }, [response.match.length]); + }, [response.actions.length, response.match.length]); const onToggleAccordion = useCallback((isOpen: boolean) => { setAccordionState(isOpen ? 'open' : 'closed'); @@ -205,6 +224,8 @@ export const ControlGeneralViewResponse = ({ }; }, [accordionState, response.match]); + const errorList = useMemo(() => Object.values(errors), [errors]); + return ( } > - 0}> - 0}> + 0}> + )} - + { // validate responses responses.forEach((response) => { // for now we force 'alert' action if 'block' action added. - if (response.actions.includes('block') && !response.actions.includes('alert')) { + if ( + response.actions && + response.actions.includes('block') && + !response.actions.includes('alert') + ) { errors.push(i18n.errorAlertActionRequired); } }); From ef99b8505d9b1385f14cf551b3c24acfadca6355 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Thu, 27 Apr 2023 10:45:06 -0700 Subject: [PATCH 17/19] [ResponseOps] Enable maintenance windows (#156033) ## Summary Enables maintenance windows by setting the feature flag to `true` --- x-pack/plugins/alerting/common/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index 1fa0806effdef..d71b69bcb51e7 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -66,4 +66,4 @@ export const INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH = export const ALERTS_FEATURE_ID = 'alerts'; export const MONITORING_HISTORY_LIMIT = 200; -export const ENABLE_MAINTENANCE_WINDOWS = false; +export const ENABLE_MAINTENANCE_WINDOWS = true; From e199282adaa1dc25eec4ee53adb5bf052710814b Mon Sep 17 00:00:00 2001 From: Saarika Bhasi <55930906+saarikabhasi@users.noreply.github.com> Date: Thu, 27 Apr 2023 13:46:22 -0400 Subject: [PATCH 18/19] [Search Application] Update pageTemplate header to grey and rename nav item to "Search preview" (#155795) ## Summary * Rename nav item from "Preview" to "Search Preview" * Update page Header to grey ### Screenshots Search preview Content Connect --- .../components/engine/engine_connect/engine_connect.tsx | 6 ++++++ .../engine/engine_search_preview/engine_search_preview.tsx | 3 +++ .../components/engine/engine_view.tsx | 2 ++ .../components/engine/search_application_content.tsx | 5 +++++ .../components/engine/search_application_layout.scss | 5 +++++ .../components/layout/engines_page_template.tsx | 1 + .../public/applications/shared/layout/nav.test.tsx | 2 +- .../public/applications/shared/layout/nav.tsx | 2 +- 8 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/search_application_layout.scss diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx index a6c29285f4f66..018470da69410 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx @@ -26,6 +26,8 @@ import { EngineViewLogic } from '../engine_view_logic'; import { SearchApplicationAPI } from './search_application_api'; +import '../search_application_layout.scss'; + const pageTitle = i18n.translate( 'xpack.enterpriseSearch.content.searchApplications.connect.pageTitle', { @@ -68,6 +70,8 @@ export const EngineConnect: React.FC = () => { pageViewTelemetry={EngineViewTabs.CONNECT} isLoading={isLoadingEngine} pageHeader={{ + bottomBorder: false, + className: 'searchApplicationHeaderBackgroundColor', pageTitle, rightSideItems: [], }} @@ -84,6 +88,8 @@ export const EngineConnect: React.FC = () => { pageViewTelemetry={EngineViewTabs.CONNECT} isLoading={isLoadingEngine} pageHeader={{ + bottomBorder: false, + className: 'searchApplicationHeaderBackgroundColor', pageTitle, rightSideItems: [], tabs: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx index 5cd4895c3aa35..25818607f102f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx @@ -72,6 +72,7 @@ import { ResultsView, Sorting, } from './search_ui_components'; +import '../search_application_layout.scss'; class InternalEngineTransporter implements Transporter { constructor( @@ -313,6 +314,8 @@ export const EngineSearchPreview: React.FC = () => { pageViewTelemetry={EngineViewTabs.PREVIEW} isLoading={isLoadingEngine} pageHeader={{ + bottomBorder: false, + className: 'searchApplicationHeaderBackgroundColor', pageTitle: ( { pageChrome={[engineName]} pageViewTelemetry={tabId} pageHeader={{ + bottomBorder: false, pageTitle: engineName, rightSideItems: [], }} @@ -91,6 +92,7 @@ export const EngineView: React.FC = () => { pageChrome={[engineName]} pageViewTelemetry={tabId} pageHeader={{ + bottomBorder: false, pageTitle: engineName, rightSideItems: [], }} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/search_application_content.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/search_application_content.tsx index 4bf79b31a1491..4fa39f2fa0cf2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/search_application_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/search_application_content.tsx @@ -30,6 +30,7 @@ import { EngineIndices } from './engine_indices'; import { EngineIndicesLogic } from './engine_indices_logic'; import { EngineSchema } from './engine_schema'; import { EngineViewLogic } from './engine_view_logic'; +import './search_application_layout.scss'; const pageTitle = i18n.translate( 'xpack.enterpriseSearch.content.searchApplications.content.pageTitle', @@ -78,6 +79,8 @@ export const SearchApplicationContent = () => { pageViewTelemetry={EngineViewTabs.CONTENT} isLoading={isLoadingEngine} pageHeader={{ + bottomBorder: false, + className: 'searchApplicationHeaderBackgroundColor', pageTitle, rightSideItems: [], }} @@ -103,6 +106,7 @@ export const SearchApplicationContent = () => { pageViewTelemetry={EngineViewTabs.CONTENT} isLoading={isLoadingEngine} pageHeader={{ + bottomBorder: false, breadcrumbs: [ { color: 'primary', @@ -119,6 +123,7 @@ export const SearchApplicationContent = () => { ), }, ], + className: 'searchApplicationHeaderBackgroundColor', pageTitle, rightSideItems: [ { { href: `/app/enterprise_search/content/engines/${engineName}/preview`, id: 'enterpriseSearchEnginePreview', - name: 'Preview', + name: 'Search Preview', }, { href: `/app/enterprise_search/content/engines/${engineName}/content`, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index 7fbc354ff725f..86eb362c1e2ae 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -196,7 +196,7 @@ export const useEnterpriseSearchEngineNav = (engineName?: string, isEmptyState?: { id: 'enterpriseSearchEnginePreview', name: i18n.translate('xpack.enterpriseSearch.nav.engine.previewTitle', { - defaultMessage: 'Preview', + defaultMessage: 'Search Preview', }), ...generateNavLink({ shouldNotCreateHref: true, From f9d7655157f502b6288a534aca1d0246d8f20459 Mon Sep 17 00:00:00 2001 From: Liam Thompson <32779855+leemthompo@users.noreply.github.com> Date: Thu, 27 Apr 2023 20:02:57 +0200 Subject: [PATCH 19/19] [Enterprise Search] Fix copy, typo in code snippet (#156049) --- .../engine/engine_connect/engine_api_integration.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx index 2fe691e262b64..0d47f02e7475b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx @@ -19,7 +19,7 @@ import { EngineApiLogic } from './engine_api_logic'; import { elasticsearchUrl } from './search_application_api'; -const SearchUISnippet = (esUrl: string, engineName: string, apiKey: string) => `6 +const SearchUISnippet = (esUrl: string, engineName: string, apiKey: string) => ` import EnginesAPIConnector from "@elastic/search-ui-engines-connector"; const connector = new EnginesAPIConnector({ @@ -74,7 +74,7 @@ export const EngineApiIntegrationStage: React.FC = () => {

{i18n.translate('xpack.enterpriseSearch.content.engine.api.step3.intro', { defaultMessage: - 'Learn how to integrate with your search application with the language clients maintained by Elastic to help build your search experience.', + 'Use the following code snippets to connect to your search application.', })}