From 7c24a61c435099acfe751b0379a8092df4d5b5e3 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 27 Jul 2020 15:19:42 -0400 Subject: [PATCH 01/75] Make ingest node pipelines api tests more robust (#73289) --- .../ingest_pipelines/ingest_pipelines.ts | 90 +++++++++++++------ .../ingest_pipelines/lib/elasticsearch.ts | 21 ++++- 2 files changed, 82 insertions(+), 29 deletions(-) diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts index 6a827298521dd..b3fab42a46114 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts @@ -14,16 +14,26 @@ const API_BASE_PATH = '/api/ingest_pipelines'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const { createPipeline, deletePipeline } = registerEsHelpers(getService); + const { createPipeline, deletePipeline, cleanupPipelines } = registerEsHelpers(getService); + + describe('Pipelines', function () { + after(async () => { + await cleanupPipelines(); + }); - describe.skip('Pipelines', function () { describe('Create', () => { const PIPELINE_ID = 'test_create_pipeline'; const REQUIRED_FIELDS_PIPELINE_ID = 'test_create_required_fields_pipeline'; - after(() => { - deletePipeline(PIPELINE_ID); - deletePipeline(REQUIRED_FIELDS_PIPELINE_ID); + after(async () => { + // Clean up any pipelines created in test cases + await Promise.all([PIPELINE_ID, REQUIRED_FIELDS_PIPELINE_ID].map(deletePipeline)).catch( + (err) => { + // eslint-disable-next-line no-console + console.log(`[Cleanup error] Error deleting pipelines: ${err.message}`); + throw err; + } + ); }); it('should create a pipeline', async () => { @@ -127,8 +137,16 @@ export default function ({ getService }: FtrProviderContext) { ], }; - before(() => createPipeline({ body: PIPELINE, id: PIPELINE_ID })); - after(() => deletePipeline(PIPELINE_ID)); + before(async () => { + // Create pipeline that can be used to test PUT request + try { + await createPipeline({ body: PIPELINE, id: PIPELINE_ID }, true); + } catch (err) { + // eslint-disable-next-line no-console + console.log('[Setup error] Error creating ingest node pipeline'); + throw err; + } + }); it('should allow an existing pipeline to be updated', async () => { const uri = `${API_BASE_PATH}/${PIPELINE_ID}`; @@ -185,7 +203,7 @@ export default function ({ getService }: FtrProviderContext) { }); describe('Get', () => { - const PIPELINE_ID = 'test_pipeline'; + const PIPELINE_ID = 'test_get_pipeline'; const PIPELINE = { description: 'test pipeline description', processors: [ @@ -198,8 +216,16 @@ export default function ({ getService }: FtrProviderContext) { version: 1, }; - before(() => createPipeline({ body: PIPELINE, id: PIPELINE_ID })); - after(() => deletePipeline(PIPELINE_ID)); + before(async () => { + // Create pipeline that can be used to test GET request + try { + await createPipeline({ body: PIPELINE, id: PIPELINE_ID }, true); + } catch (err) { + // eslint-disable-next-line no-console + console.log('[Setup error] Error creating ingest node pipeline'); + throw err; + } + }); describe('all pipelines', () => { it('should return an array of pipelines', async () => { @@ -245,29 +271,40 @@ export default function ({ getService }: FtrProviderContext) { version: 1, }; + const pipelineA = { body: PIPELINE, id: 'test_delete_pipeline_a' }; + const pipelineB = { body: PIPELINE, id: 'test_delete_pipeline_b' }; + const pipelineC = { body: PIPELINE, id: 'test_delete_pipeline_c' }; + const pipelineD = { body: PIPELINE, id: 'test_delete_pipeline_d' }; + + before(async () => { + // Create several pipelines that can be used to test deletion + await Promise.all( + [pipelineA, pipelineB, pipelineC, pipelineD].map((pipeline) => createPipeline(pipeline)) + ).catch((err) => { + // eslint-disable-next-line no-console + console.log(`[Setup error] Error creating pipelines: ${err.message}`); + throw err; + }); + }); + it('should delete a pipeline', async () => { - // Create pipeline to be deleted - const PIPELINE_ID = 'test_delete_pipeline'; - createPipeline({ body: PIPELINE, id: PIPELINE_ID }); + const { id } = pipelineA; - const uri = `${API_BASE_PATH}/${PIPELINE_ID}`; + const uri = `${API_BASE_PATH}/${id}`; const { body } = await supertest.delete(uri).set('kbn-xsrf', 'xxx').expect(200); expect(body).to.eql({ - itemsDeleted: [PIPELINE_ID], + itemsDeleted: [id], errors: [], }); }); it('should delete multiple pipelines', async () => { - // Create pipelines to be deleted - const PIPELINE_ONE_ID = 'test_delete_pipeline_1'; - const PIPELINE_TWO_ID = 'test_delete_pipeline_2'; - createPipeline({ body: PIPELINE, id: PIPELINE_ONE_ID }); - createPipeline({ body: PIPELINE, id: PIPELINE_TWO_ID }); + const { id: pipelineBId } = pipelineB; + const { id: pipelineCId } = pipelineC; - const uri = `${API_BASE_PATH}/${PIPELINE_ONE_ID},${PIPELINE_TWO_ID}`; + const uri = `${API_BASE_PATH}/${pipelineBId},${pipelineCId}`; const { body: { itemsDeleted, errors }, @@ -276,24 +313,21 @@ export default function ({ getService }: FtrProviderContext) { expect(errors).to.eql([]); // The itemsDeleted array order isn't guaranteed, so we assert against each pipeline name instead - [PIPELINE_ONE_ID, PIPELINE_TWO_ID].forEach((pipelineName) => { + [pipelineBId, pipelineCId].forEach((pipelineName) => { expect(itemsDeleted.includes(pipelineName)).to.be(true); }); }); it('should return an error for any pipelines not sucessfully deleted', async () => { const PIPELINE_DOES_NOT_EXIST = 'pipeline_does_not_exist'; + const { id: existingPipelineId } = pipelineD; - // Create pipeline to be deleted - const PIPELINE_ONE_ID = 'test_delete_pipeline_1'; - createPipeline({ body: PIPELINE, id: PIPELINE_ONE_ID }); - - const uri = `${API_BASE_PATH}/${PIPELINE_ONE_ID},${PIPELINE_DOES_NOT_EXIST}`; + const uri = `${API_BASE_PATH}/${existingPipelineId},${PIPELINE_DOES_NOT_EXIST}`; const { body } = await supertest.delete(uri).set('kbn-xsrf', 'xxx').expect(200); expect(body).to.eql({ - itemsDeleted: [PIPELINE_ONE_ID], + itemsDeleted: [existingPipelineId], errors: [ { name: PIPELINE_DOES_NOT_EXIST, diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts index 2f42596a66b54..6de91e1154a85 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts @@ -26,14 +26,33 @@ interface Pipeline { * @param {ElasticsearchClient} es The Elasticsearch client instance */ export const registerEsHelpers = (getService: FtrProviderContext['getService']) => { + let pipelinesCreated: string[] = []; + const es = getService('legacyEs'); - const createPipeline = (pipeline: Pipeline) => es.ingest.putPipeline(pipeline); + const createPipeline = (pipeline: Pipeline, cachePipeline?: boolean) => { + if (cachePipeline) { + pipelinesCreated.push(pipeline.id); + } + + return es.ingest.putPipeline(pipeline); + }; const deletePipeline = (pipelineId: string) => es.ingest.deletePipeline({ id: pipelineId }); + const cleanupPipelines = () => + Promise.all(pipelinesCreated.map(deletePipeline)) + .then(() => { + pipelinesCreated = []; + }) + .catch((err) => { + // eslint-disable-next-line no-console + console.log(`[Cleanup error] Error deleting ES resources: ${err.message}`); + }); + return { createPipeline, deletePipeline, + cleanupPipelines, }; }; From f23359c099ece3807f68b9b8ab24a72ff005d911 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 27 Jul 2020 14:20:26 -0500 Subject: [PATCH 02/75] [APM] Fix license management URL (#73294) The URL to license management has changed, so update our links, both in the prompt on the Service Map, and in the app-wide expired license message. Use `useKibanaUrl` in both places and update that hook to make the `hash` argument optional, since many apps don't use a hash now. I looked for a more reliable way to get the URL for that app but couldn't come up with anything. I'd appreciate any suggestions if there's a better method. --- .../apm/public/components/shared/LicensePrompt/index.tsx | 3 +-- .../context/LicenseContext/InvalidLicenseNotification.tsx | 5 ++--- x-pack/plugins/apm/public/hooks/useKibanaUrl.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx b/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx index 50be268d9ccd0..8409326243614 100644 --- a/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx @@ -16,8 +16,7 @@ interface Props { export function LicensePrompt({ text, showBetaBadge = false }: Props) { const licensePageUrl = useKibanaUrl( - '/app/kibana', - '/management/stack/license_management/home' + '/app/management/stack/license_management' ); const renderLicenseBody = ( diff --git a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx index 481e89e09685e..1195038a6b753 100644 --- a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx +++ b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx @@ -6,11 +6,10 @@ import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; +import { useKibanaUrl } from '../../hooks/useKibanaUrl'; export function InvalidLicenseNotification() { - const { core } = useApmPluginContext(); - const manageLicenseURL = core.http.basePath.prepend( + const manageLicenseURL = useKibanaUrl( '/app/management/stack/license_management' ); diff --git a/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts b/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts index 186a752f52487..b4a354c231633 100644 --- a/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts +++ b/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts @@ -9,7 +9,7 @@ import { useApmPluginContext } from './useApmPluginContext'; export function useKibanaUrl( /** The path to the plugin */ path: string, - /** The hash path */ hash: string + /** The hash path */ hash?: string ) { const { core } = useApmPluginContext(); return url.format({ From 1c690c68af6ea05248ca04b259fb1f01970a044c Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 27 Jul 2020 15:39:52 -0400 Subject: [PATCH 03/75] [Uptime] Increase timeout in attempt to fix skipped a11y test (#73078) * Increase timeout in attempt to fix skipped a11y test. * Temporarily only run uptime tests for faster flaky testing. * Uncomment other test suites. * Unskip test and delete comment. Co-authored-by: Elastic Machine --- x-pack/test/accessibility/apps/uptime.ts | 3 +-- x-pack/test/functional/services/uptime/navigation.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/test/accessibility/apps/uptime.ts b/x-pack/test/accessibility/apps/uptime.ts index e6ef1cfe8cfe2..ebd120fa0feea 100644 --- a/x-pack/test/accessibility/apps/uptime.ts +++ b/x-pack/test/accessibility/apps/uptime.ts @@ -17,8 +17,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const es = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/72994 - describe.skip('uptime', () => { + describe('uptime', () => { before(async () => { await esArchiver.load('uptime/blank'); await makeChecks(es, A11Y_TEST_MONITOR_ID, 150, 1, 1000, { diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index f8e0c0cff41f4..ab511abf130a5 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -41,7 +41,7 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv goToSettings: async () => { await goToUptimeRoot(); await testSubjects.click('settings-page-link', 5000); - await testSubjects.existOrFail('uptimeSettingsPage', { timeout: 2000 }); + await testSubjects.existOrFail('uptimeSettingsPage', { timeout: 10000 }); }, checkIfOnMonitorPage: async (monitorId: string) => { From 2ae470e897976abb939c31708bff41fd0d0dcd07 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Mon, 27 Jul 2020 16:04:10 -0500 Subject: [PATCH 04/75] Add Kea.js support to Enterprise Search plugin (#72160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Kea packages - kea and kea-waitfor * Add Kea declarations and types Hopefully TypeScript support coming soon from author * Add Kea to entry point * Add logic for overview * Update components to use Kea * Fix a couple of tests that weren’t getting complete coverage * Remove kea-waitfor Turns out we don’t need it * Remove unused declaration * Update x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/overview_logic.mock.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.test.ts Co-authored-by: Constance * [Opinionated] Remove extra actions defs - they're already being defined in IOverviewActions, so no need to repeat them * DRY out a new reusable/generics IKeaLogic/Listeners interface - Multiple logic files can now do IKeaListeners and not have to declare their own IListenerParams! + bonus IKeaSelectors just for consistency * DRY out Kea reducers definitions to generics interface * [Refactor] Improve KeaReducers generic to actually type-check/check key names - Typescript will now throw an error if you put in a key name that isn't declared in your actions/values interface - default & new states now will be type checked!! :tada: * [Refactor] Update selectors() and listeners() to also check types and keys * [Refactor] Move param defs to bottom of file instead of inline - so that inline definitions mostly focus on type checks, and more boilerplate defs are DRYed out - I played around with 2.1 obj definitions and got terrible results here :( * Update tests and remove selectors per code review * Remove last statsColumns instance * Remove last instance of hideOnboarding Co-authored-by: Constance Co-authored-by: Constance Chen Co-authored-by: Elastic Machine --- x-pack/package.json | 3 +- .../public/applications/kea.d.ts | 13 ++ .../public/applications/shared/types.ts | 56 ++++++ .../components/overview/__mocks__/index.ts | 7 + .../overview/__mocks__/overview_logic.mock.ts | 47 +++++ .../overview/onboarding_steps.test.tsx | 77 ++++---- .../components/overview/onboarding_steps.tsx | 27 +-- .../overview/organization_stats.test.tsx | 8 +- .../overview/organization_stats.tsx | 115 ++++++------ .../components/overview/overview.test.tsx | 45 +++-- .../components/overview/overview.tsx | 97 ++-------- .../overview/overview_logic.test.ts | 141 +++++++++++++++ .../components/overview/overview_logic.ts | 168 ++++++++++++++++++ .../overview/recent_activity.test.tsx | 21 ++- .../components/overview/recent_activity.tsx | 13 +- .../content_section/content_section.test.tsx | 3 +- .../view_content_header.test.tsx | 3 +- .../applications/workplace_search/index.tsx | 11 +- .../applications/workplace_search/types.ts | 11 ++ yarn.lock | 5 + 20 files changed, 634 insertions(+), 237 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/kea.d.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/overview_logic.mock.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts diff --git a/x-pack/package.json b/x-pack/package.json index d1f638ccad8d0..dee99d6f0ddac 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -282,6 +282,7 @@ "json-stable-stringify": "^1.0.1", "jsonwebtoken": "^8.5.1", "jsts": "^1.6.2", + "kea": "^2.0.1", "lodash": "^4.17.15", "lz-string": "^1.4.4", "mapbox-gl": "^1.10.0", @@ -384,4 +385,4 @@ "cypress-multi-reporters" ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/enterprise_search/public/applications/kea.d.ts b/x-pack/plugins/enterprise_search/public/applications/kea.d.ts new file mode 100644 index 0000000000000..961d93ccc12e6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/kea.d.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +declare module 'kea' { + export function useValues(logic?: object): object; + export function useActions(logic?: object): object; + export function getContext(): { store: object }; + export function resetContext(context: object): object; + export function kea(logic: object): object; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index 3f28710d92295..74bb53ef3a954 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -12,3 +12,59 @@ export interface IFlashMessagesProps { isWrapped?: boolean; children?: React.ReactNode; } + +export interface IKeaLogic { + mount(): void; + values: IKeaValues; + actions: IKeaActions; +} + +/** + * This reusable interface mostly saves us a few characters / allows us to skip + * defining params inline. Unfortunately, the return values *do not work* as + * expected (hence the voids). While I can tell selectors to use TKeaSelectors, + * the return value is *not* properly type checked if it's not declared inline. :/ + * + * Also note that if you switch to Kea 2.1's plain object notation - + * `selectors: {}` vs. `selectors: () => ({})` + * - type checking also stops working and type errors become significantly less + * helpful - showing less specific error messages and highlighting. 👎 + */ +export interface IKeaParams { + selectors?(params: { selectors: IKeaValues }): void; + listeners?(params: { actions: IKeaActions; values: IKeaValues }): void; +} + +/** + * This reducers() type checks that: + * + * 1. The value object keys are defined within IKeaValues + * 2. The default state (array[0]) matches the type definition within IKeaValues + * 3. The action object keys (array[1]) are defined within IKeaActions + * 3. The new state returned by the action matches the type definition within IKeaValues + */ +export type TKeaReducers = { + [Value in keyof IKeaValues]?: [ + IKeaValues[Value], + { + [Action in keyof IKeaActions]?: (state: IKeaValues, payload: IKeaValues) => IKeaValues[Value]; + } + ]; +}; + +/** + * This selectors() type checks that: + * + * 1. The object keys are defined within IKeaValues + * 2. The selected values are defined within IKeaValues + * 3. The returned value match the type definition within IKeaValues + * + * The unknown[] and any[] are unfortunately because I have no idea how to + * assert for arbitrary type/values as an array + */ +export type TKeaSelectors = { + [Value in keyof IKeaValues]?: [ + (selectors: IKeaValues) => unknown[], + (...args: any[]) => IKeaValues[Value] // eslint-disable-line @typescript-eslint/no-explicit-any + ]; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/index.ts new file mode 100644 index 0000000000000..e5169a51ce522 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { setMockValues, mockLogicValues, mockLogicActions } from './overview_logic.mock'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/overview_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/overview_logic.mock.ts new file mode 100644 index 0000000000000..43cff5de6668d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/overview_logic.mock.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IOverviewValues } from '../overview_logic'; +import { IAccount, IOrganization, IUser } from '../../../types'; + +export const mockLogicValues = { + accountsCount: 0, + activityFeed: [], + canCreateContentSources: false, + canCreateInvitations: false, + currentUser: {} as IUser, + fpAccount: {} as IAccount, + hasOrgSources: false, + hasUsers: false, + isFederatedAuth: true, + isOldAccount: false, + organization: {} as IOrganization, + pendingInvitationsCount: 0, + personalSourcesCount: 0, + sourcesCount: 0, + dataLoading: true, + hasErrorConnecting: false, + flashMessages: {}, +} as IOverviewValues; + +export const mockLogicActions = { + initializeOverview: jest.fn(() => ({})), +}; + +jest.mock('kea', () => ({ + ...(jest.requireActual('kea') as object), + useActions: jest.fn(() => ({ ...mockLogicActions })), + useValues: jest.fn(() => ({ ...mockLogicValues })), +})); + +import { useValues } from 'kea'; + +export const setMockValues = (values: object) => { + (useValues as jest.Mock).mockImplementationOnce(() => ({ + ...mockLogicValues, + ...values, + })); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.test.tsx index 6174dc1c795eb..3cf88cf120cc4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.test.tsx @@ -5,6 +5,8 @@ */ import '../../../__mocks__/shallow_usecontext.mock'; +import './__mocks__/overview_logic.mock'; +import { setMockValues } from './__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; @@ -16,7 +18,6 @@ import { sendTelemetry } from '../../../shared/telemetry'; import { OnboardingSteps, OrgNameOnboarding } from './onboarding_steps'; import { OnboardingCard } from './onboarding_card'; -import { defaultServerData } from './overview'; const account = { id: '1', @@ -30,7 +31,8 @@ const account = { describe('OnboardingSteps', () => { describe('Shared Sources', () => { it('renders 0 sources state', () => { - const wrapper = shallow(); + setMockValues({ canCreateContentSources: true }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard)).toHaveLength(1); expect(wrapper.find(OnboardingCard).prop('actionPath')).toBe(ORG_SOURCES_PATH); @@ -40,9 +42,8 @@ describe('OnboardingSteps', () => { }); it('renders completed sources state', () => { - const wrapper = shallow( - - ); + setMockValues({ sourcesCount: 2, hasOrgSources: true }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard).prop('description')).toEqual( 'You have added 2 shared sources. Happy searching.' @@ -50,9 +51,8 @@ describe('OnboardingSteps', () => { }); it('disables link when the user cannot create sources', () => { - const wrapper = shallow( - - ); + setMockValues({ canCreateContentSources: false }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard).prop('actionPath')).toBe(undefined); }); @@ -60,15 +60,14 @@ describe('OnboardingSteps', () => { describe('Users & Invitations', () => { it('renders 0 users when not on federated auth', () => { - const wrapper = shallow( - - ); + setMockValues({ + canCreateInvitations: true, + isFederatedAuth: false, + fpAccount: account, + accountsCount: 0, + hasUsers: false, + }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard)).toHaveLength(2); expect(wrapper.find(OnboardingCard).last().prop('actionPath')).toBe(USERS_PATH); @@ -78,15 +77,13 @@ describe('OnboardingSteps', () => { }); it('renders completed users state', () => { - const wrapper = shallow( - - ); + setMockValues({ + isFederatedAuth: false, + fpAccount: account, + accountsCount: 1, + hasUsers: true, + }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard).last().prop('description')).toEqual( 'Nice, you’ve invited colleagues to search with you.' @@ -94,21 +91,15 @@ describe('OnboardingSteps', () => { }); it('disables link when the user cannot create invitations', () => { - const wrapper = shallow( - - ); - + setMockValues({ isFederatedAuth: false, canCreateInvitations: false }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard).last().prop('actionPath')).toBe(undefined); }); }); describe('Org Name', () => { it('renders button to change name', () => { - const wrapper = shallow(); + const wrapper = shallow(); const button = wrapper .find(OrgNameOnboarding) @@ -120,15 +111,13 @@ describe('OnboardingSteps', () => { }); it('hides card when name has been changed', () => { - const wrapper = shallow( - - ); + setMockValues({ + organization: { + name: 'foo', + defaultOrgName: 'bar', + }, + }); + const wrapper = shallow(); expect(wrapper.find(OrgNameOnboarding)).toHaveLength(0); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx index 1b00347437338..7fe1eae502329 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx @@ -7,6 +7,7 @@ import React, { useContext } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useValues } from 'kea'; import { EuiSpacer, @@ -28,7 +29,7 @@ import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; import { ContentSection } from '../shared/content_section'; -import { IAppServerData } from './overview'; +import { OverviewLogic, IOverviewValues } from './overview_logic'; import { OnboardingCard } from './onboarding_card'; @@ -57,17 +58,19 @@ const ONBOARDING_USERS_CARD_DESCRIPTION = i18n.translate( { defaultMessage: 'Invite your colleagues into this organization to search with you.' } ); -export const OnboardingSteps: React.FC = ({ - hasUsers, - hasOrgSources, - canCreateContentSources, - canCreateInvitations, - accountsCount, - sourcesCount, - fpAccount: { isCurated }, - organization: { name, defaultOrgName }, - isFederatedAuth, -}) => { +export const OnboardingSteps: React.FC = () => { + const { + hasUsers, + hasOrgSources, + canCreateContentSources, + canCreateInvitations, + accountsCount, + sourcesCount, + fpAccount: { isCurated }, + organization: { name, defaultOrgName }, + isFederatedAuth, + } = useValues(OverviewLogic) as IOverviewValues; + const accountsPath = !isFederatedAuth && (canCreateInvitations || isCurated) ? USERS_PATH : undefined; const sourcesPath = canCreateContentSources || isCurated ? ORG_SOURCES_PATH : undefined; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.test.tsx index 112e9a910667a..d9b05c5da777d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.test.tsx @@ -5,6 +5,8 @@ */ import '../../../__mocks__/shallow_usecontext.mock'; +import './__mocks__/overview_logic.mock'; +import { setMockValues } from './__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; @@ -12,18 +14,18 @@ import { EuiFlexGrid } from '@elastic/eui'; import { OrganizationStats } from './organization_stats'; import { StatisticCard } from './statistic_card'; -import { defaultServerData } from './overview'; describe('OrganizationStats', () => { it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(StatisticCard)).toHaveLength(2); expect(wrapper.find(EuiFlexGrid).prop('columns')).toEqual(2); }); it('renders additional cards for federated auth', () => { - const wrapper = shallow(); + setMockValues({ isFederatedAuth: false }); + const wrapper = shallow(); expect(wrapper.find(StatisticCard)).toHaveLength(4); expect(wrapper.find(EuiFlexGrid).prop('columns')).toEqual(4); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.tsx index aa9be81f32bae..4c5efce9baf12 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.tsx @@ -6,6 +6,7 @@ import React from 'react'; import { EuiFlexGrid } from '@elastic/eui'; +import { useValues } from 'kea'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -13,62 +14,66 @@ import { i18n } from '@kbn/i18n'; import { ContentSection } from '../shared/content_section'; import { ORG_SOURCES_PATH, USERS_PATH } from '../../routes'; -import { IAppServerData } from './overview'; +import { OverviewLogic, IOverviewValues } from './overview_logic'; import { StatisticCard } from './statistic_card'; -export const OrganizationStats: React.FC = ({ - sourcesCount, - pendingInvitationsCount, - accountsCount, - personalSourcesCount, - isFederatedAuth, -}) => ( - - } - headerSpacer="m" - > - - - {!isFederatedAuth && ( - <> - - - - )} - { + const { + sourcesCount, + pendingInvitationsCount, + accountsCount, + personalSourcesCount, + isFederatedAuth, + } = useValues(OverviewLogic) as IOverviewValues; + + return ( + + } + headerSpacer="m" + > + + + {!isFederatedAuth && ( + <> + + + )} - count={personalSourcesCount} - /> - - -); + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.test.tsx index e5e5235c52368..744fd8aeb1951 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.test.tsx @@ -5,11 +5,11 @@ */ import '../../../__mocks__/react_router_history.mock'; +import './__mocks__/overview_logic.mock'; +import { mockLogicActions, setMockValues } from './__mocks__'; import React from 'react'; -import { shallow } from 'enzyme'; - -import { mountWithAsyncContext, mockKibanaContext } from '../../../__mocks__'; +import { shallow, mount } from 'enzyme'; import { ErrorState } from '../error_state'; import { Loading } from '../shared/loading'; @@ -18,11 +18,9 @@ import { ViewContentHeader } from '../shared/view_content_header'; import { OnboardingSteps } from './onboarding_steps'; import { OrganizationStats } from './organization_stats'; import { RecentActivity } from './recent_activity'; -import { Overview, defaultServerData } from './overview'; +import { Overview } from './overview'; describe('Overview', () => { - const mockHttp = mockKibanaContext.http; - describe('non-happy-path states', () => { it('isLoading', () => { const wrapper = shallow(); @@ -30,24 +28,24 @@ describe('Overview', () => { expect(wrapper.find(Loading)).toHaveLength(1); }); - it('hasErrorConnecting', async () => { - const wrapper = await mountWithAsyncContext(, { - http: { - ...mockHttp, - get: () => Promise.reject({ invalidPayload: true }), - }, - }); + it('hasErrorConnecting', () => { + setMockValues({ hasErrorConnecting: true }); + const wrapper = shallow(); expect(wrapper.find(ErrorState)).toHaveLength(1); }); }); describe('happy-path states', () => { - it('renders onboarding state', async () => { - const mockApi = jest.fn(() => defaultServerData); - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, - }); + it('calls initialize function', async () => { + mount(); + + expect(mockLogicActions.initializeOverview).toHaveBeenCalled(); + }); + + it('renders onboarding state', () => { + setMockValues({ dataLoading: false }); + const wrapper = shallow(); expect(wrapper.find(ViewContentHeader)).toHaveLength(1); expect(wrapper.find(OnboardingSteps)).toHaveLength(1); @@ -55,9 +53,9 @@ describe('Overview', () => { expect(wrapper.find(RecentActivity)).toHaveLength(1); }); - it('renders when onboarding complete', async () => { - const obCompleteData = { - ...defaultServerData, + it('renders when onboarding complete', () => { + setMockValues({ + dataLoading: false, hasUsers: true, hasOrgSources: true, isOldAccount: true, @@ -65,11 +63,8 @@ describe('Overview', () => { name: 'foo', defaultOrgName: 'bar', }, - }; - const mockApi = jest.fn(() => obCompleteData); - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, }); + const wrapper = shallow(); expect(wrapper.find(OnboardingSteps)).toHaveLength(0); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.tsx index bacd65a2be75f..b75a2841dad9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.tsx @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect } from 'react'; import { EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useActions, useValues } from 'kea'; import { SetWorkplaceSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs'; import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; import { KibanaContext, IKibanaContext } from '../../../index'; -import { IAccount } from '../../types'; +import { OverviewLogic, IOverviewActions, IOverviewValues } from './overview_logic'; import { ErrorState } from '../error_state'; @@ -22,57 +23,7 @@ import { ViewContentHeader } from '../shared/view_content_header'; import { OnboardingSteps } from './onboarding_steps'; import { OrganizationStats } from './organization_stats'; -import { RecentActivity, IFeedActivity } from './recent_activity'; - -export interface IAppServerData { - hasUsers: boolean; - hasOrgSources: boolean; - canCreateContentSources: boolean; - canCreateInvitations: boolean; - isOldAccount: boolean; - sourcesCount: number; - pendingInvitationsCount: number; - accountsCount: number; - personalSourcesCount: number; - activityFeed: IFeedActivity[]; - organization: { - name: string; - defaultOrgName: string; - }; - isFederatedAuth: boolean; - currentUser: { - firstName: string; - email: string; - name: string; - color: string; - }; - fpAccount: IAccount; -} - -export const defaultServerData = { - accountsCount: 1, - activityFeed: [], - canCreateContentSources: true, - canCreateInvitations: true, - currentUser: { - firstName: '', - email: '', - name: '', - color: '', - }, - fpAccount: {} as IAccount, - hasOrgSources: false, - hasUsers: false, - isFederatedAuth: true, - isOldAccount: false, - organization: { - name: '', - defaultOrgName: '', - }, - pendingInvitationsCount: 0, - personalSourcesCount: 0, - sourcesCount: 0, -} as IAppServerData; +import { RecentActivity } from './recent_activity'; const ONBOARDING_HEADER_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingHeader.title', @@ -96,34 +47,24 @@ const HEADER_DESCRIPTION = i18n.translate( export const Overview: React.FC = () => { const { http } = useContext(KibanaContext) as IKibanaContext; - const [isLoading, setIsLoading] = useState(true); - const [hasErrorConnecting, setHasErrorConnecting] = useState(false); - const [appData, setAppData] = useState(defaultServerData); - - const getAppData = async () => { - try { - const response = await http.get('/api/workplace_search/overview'); - setAppData(response); - } catch (error) { - setHasErrorConnecting(true); - } finally { - setIsLoading(false); - } - }; - - useEffect(() => { - getAppData(); - }, []); - - if (hasErrorConnecting) return ; - if (isLoading) return ; + const { initializeOverview } = useActions(OverviewLogic) as IOverviewActions; const { + dataLoading, + hasErrorConnecting, hasUsers, hasOrgSources, isOldAccount, organization: { name: orgName, defaultOrgName }, - } = appData as IAppServerData; + } = useValues(OverviewLogic) as IOverviewValues; + + useEffect(() => { + initializeOverview({ http }); + }, [initializeOverview]); + + if (hasErrorConnecting) return ; + if (dataLoading) return ; + const hideOnboarding = hasUsers && hasOrgSources && isOldAccount && orgName !== defaultOrgName; const headerTitle = hideOnboarding ? HEADER_TITLE : ONBOARDING_HEADER_TITLE; @@ -140,11 +81,11 @@ export const Overview: React.FC = () => { description={headerDescription} action={} /> - {!hideOnboarding && } + {!hideOnboarding && } - + - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.test.ts new file mode 100644 index 0000000000000..285ec9b973378 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.test.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resetContext } from 'kea'; +import { act } from 'react-dom/test-utils'; + +import { mockKibanaContext } from '../../../__mocks__'; + +import { mockLogicValues } from './__mocks__'; +import { OverviewLogic } from './overview_logic'; + +describe('OverviewLogic', () => { + let unmount: any; + + beforeEach(() => { + resetContext({}); + unmount = OverviewLogic.mount() as any; + jest.clearAllMocks(); + }); + + afterEach(() => { + unmount(); + }); + + it('has expected default values', () => { + expect(OverviewLogic.values).toEqual(mockLogicValues); + }); + + describe('setServerData', () => { + const feed = [{ foo: 'bar' }] as any; + const user = { firstName: 'Joe', email: 'e@e.e', name: 'Joe Jo', color: 'pearl' }; + const account = { + name: 'Jane doe', + id: '1243', + isAdmin: true, + canCreatePersonalSources: true, + groups: [], + supportEligible: true, + }; + const org = { name: 'ACME', defaultOrgName: 'Org' }; + + const data = { + accountsCount: 1, + activityFeed: feed, + canCreateContentSources: true, + canCreateInvitations: true, + currentUser: user, + fpAccount: account, + hasOrgSources: true, + hasUsers: true, + isFederatedAuth: false, + isOldAccount: true, + organization: org, + pendingInvitationsCount: 1, + personalSourcesCount: 1, + sourcesCount: 1, + }; + + beforeEach(() => { + OverviewLogic.actions.setServerData(data); + }); + + it('will set `dataLoading` to false', () => { + expect(OverviewLogic.values.dataLoading).toEqual(false); + }); + + it('will set server values', () => { + expect(OverviewLogic.values.organization).toEqual(org); + expect(OverviewLogic.values.isFederatedAuth).toEqual(false); + expect(OverviewLogic.values.currentUser).toEqual(user); + expect(OverviewLogic.values.fpAccount).toEqual(account); + expect(OverviewLogic.values.canCreateInvitations).toEqual(true); + expect(OverviewLogic.values.hasUsers).toEqual(true); + expect(OverviewLogic.values.hasOrgSources).toEqual(true); + expect(OverviewLogic.values.canCreateContentSources).toEqual(true); + expect(OverviewLogic.values.isOldAccount).toEqual(true); + expect(OverviewLogic.values.sourcesCount).toEqual(1); + expect(OverviewLogic.values.pendingInvitationsCount).toEqual(1); + expect(OverviewLogic.values.accountsCount).toEqual(1); + expect(OverviewLogic.values.personalSourcesCount).toEqual(1); + expect(OverviewLogic.values.activityFeed).toEqual(feed); + }); + }); + + describe('setFlashMessages', () => { + it('will set `flashMessages`', () => { + const flashMessages = { error: ['error'] }; + OverviewLogic.actions.setFlashMessages(flashMessages); + + expect(OverviewLogic.values.flashMessages).toEqual(flashMessages); + }); + }); + + describe('setHasErrorConnecting', () => { + it('will set `hasErrorConnecting`', () => { + OverviewLogic.actions.setHasErrorConnecting(true); + + expect(OverviewLogic.values.hasErrorConnecting).toEqual(true); + expect(OverviewLogic.values.dataLoading).toEqual(false); + }); + }); + + describe('initializeOverview', () => { + it('calls API and sets values', async () => { + const mockHttp = mockKibanaContext.http; + const mockApi = jest.fn(() => mockLogicValues as any); + const setServerDataSpy = jest.spyOn(OverviewLogic.actions, 'setServerData'); + + await act(async () => + OverviewLogic.actions.initializeOverview({ + http: { + ...mockHttp, + get: mockApi, + }, + }) + ); + + expect(mockApi).toHaveBeenCalledWith('/api/workplace_search/overview'); + expect(setServerDataSpy).toHaveBeenCalled(); + }); + + it('handles error state', async () => { + const mockHttp = mockKibanaContext.http; + const setHasErrorConnectingSpy = jest.spyOn(OverviewLogic.actions, 'setHasErrorConnecting'); + + await act(async () => + OverviewLogic.actions.initializeOverview({ + http: { + ...mockHttp, + get: () => Promise.reject(), + }, + }) + ); + + expect(setHasErrorConnectingSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts new file mode 100644 index 0000000000000..f1b4f447f7445 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpSetup } from 'src/core/public'; + +import { kea } from 'kea'; + +import { IAccount, IOrganization, IUser } from '../../types'; +import { IFlashMessagesProps, IKeaLogic, TKeaReducers, IKeaParams } from '../../../shared/types'; + +import { IFeedActivity } from './recent_activity'; + +export interface IOverviewServerData { + hasUsers: boolean; + hasOrgSources: boolean; + canCreateContentSources: boolean; + canCreateInvitations: boolean; + isOldAccount: boolean; + sourcesCount: number; + pendingInvitationsCount: number; + accountsCount: number; + personalSourcesCount: number; + activityFeed: IFeedActivity[]; + organization: IOrganization; + isFederatedAuth: boolean; + currentUser: IUser; + fpAccount: IAccount; +} + +export interface IOverviewActions { + setServerData(serverData: IOverviewServerData): void; + setFlashMessages(flashMessages: IFlashMessagesProps): void; + setHasErrorConnecting(hasErrorConnecting: boolean): void; + initializeOverview({ http }: { http: HttpSetup }): void; +} + +export interface IOverviewValues extends IOverviewServerData { + dataLoading: boolean; + hasErrorConnecting: boolean; + flashMessages: IFlashMessagesProps; +} + +export const OverviewLogic = kea({ + actions: (): IOverviewActions => ({ + setServerData: (serverData) => serverData, + setFlashMessages: (flashMessages) => ({ flashMessages }), + setHasErrorConnecting: (hasErrorConnecting) => ({ hasErrorConnecting }), + initializeOverview: ({ http }) => ({ http }), + }), + reducers: (): TKeaReducers => ({ + organization: [ + {} as IOrganization, + { + setServerData: (_, { organization }) => organization, + }, + ], + isFederatedAuth: [ + true, + { + setServerData: (_, { isFederatedAuth }) => isFederatedAuth, + }, + ], + currentUser: [ + {} as IUser, + { + setServerData: (_, { currentUser }) => currentUser, + }, + ], + fpAccount: [ + {} as IAccount, + { + setServerData: (_, { fpAccount }) => fpAccount, + }, + ], + canCreateInvitations: [ + false, + { + setServerData: (_, { canCreateInvitations }) => canCreateInvitations, + }, + ], + flashMessages: [ + {}, + { + setFlashMessages: (_, { flashMessages }) => flashMessages, + }, + ], + hasUsers: [ + false, + { + setServerData: (_, { hasUsers }) => hasUsers, + }, + ], + hasOrgSources: [ + false, + { + setServerData: (_, { hasOrgSources }) => hasOrgSources, + }, + ], + canCreateContentSources: [ + false, + { + setServerData: (_, { canCreateContentSources }) => canCreateContentSources, + }, + ], + isOldAccount: [ + false, + { + setServerData: (_, { isOldAccount }) => isOldAccount, + }, + ], + sourcesCount: [ + 0, + { + setServerData: (_, { sourcesCount }) => sourcesCount, + }, + ], + pendingInvitationsCount: [ + 0, + { + setServerData: (_, { pendingInvitationsCount }) => pendingInvitationsCount, + }, + ], + accountsCount: [ + 0, + { + setServerData: (_, { accountsCount }) => accountsCount, + }, + ], + personalSourcesCount: [ + 0, + { + setServerData: (_, { personalSourcesCount }) => personalSourcesCount, + }, + ], + activityFeed: [ + [], + { + setServerData: (_, { activityFeed }) => activityFeed, + }, + ], + dataLoading: [ + true, + { + setServerData: () => false, + setHasErrorConnecting: () => false, + }, + ], + hasErrorConnecting: [ + false, + { + setHasErrorConnecting: (_, { hasErrorConnecting }) => hasErrorConnecting, + }, + ], + }), + listeners: ({ actions }): Partial => ({ + initializeOverview: async ({ http }: { http: HttpSetup }) => { + try { + const response = await http.get('/api/workplace_search/overview'); + actions.setServerData(response); + } catch (error) { + actions.setHasErrorConnecting(true); + } + }, + }), +} as IKeaParams) as IKeaLogic; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.test.tsx index e9bdedb199dad..22a82af18527d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.test.tsx @@ -5,6 +5,8 @@ */ import '../../../__mocks__/shallow_usecontext.mock'; +import './__mocks__/overview_logic.mock'; +import { setMockValues } from './__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; @@ -12,14 +14,13 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import { RecentActivity, RecentActivityItem } from './recent_activity'; -import { defaultServerData } from './overview'; jest.mock('../../../shared/telemetry', () => ({ sendTelemetry: jest.fn() })); import { sendTelemetry } from '../../../shared/telemetry'; -const org = { name: 'foo', defaultOrgName: 'bar' }; +const organization = { name: 'foo', defaultOrgName: 'bar' }; -const feed = [ +const activityFeed = [ { id: 'demo', sourceId: 'd2d2d23d', @@ -30,17 +31,19 @@ const feed = [ ]; describe('RecentActivity', () => { - it('renders with no feed data', () => { - const wrapper = shallow(); + it('renders with no activityFeed data', () => { + const wrapper = shallow(); expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); // Branch coverage - renders without error for custom org name - shallow(); + setMockValues({ organization }); + shallow(); }); - it('renders an activity feed with links', () => { - const wrapper = shallow(); + it('renders an activityFeed with links', () => { + setMockValues({ activityFeed }); + const wrapper = shallow(); const activity = wrapper.find(RecentActivityItem).dive(); expect(activity).toHaveLength(1); @@ -51,7 +54,7 @@ describe('RecentActivity', () => { }); it('renders activity item error state', () => { - const props = { ...feed[0], status: 'error' }; + const props = { ...activityFeed[0], status: 'error' }; const wrapper = shallow(); expect(wrapper.find('.activity--error')).toHaveLength(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx index 8d69582c93684..2c0fbe1275cbf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx @@ -7,6 +7,7 @@ import React, { useContext } from 'react'; import moment from 'moment'; +import { useValues } from 'kea'; import { EuiEmptyPrompt, EuiLink, EuiPanel, EuiSpacer, EuiLinkProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -17,7 +18,7 @@ import { sendTelemetry } from '../../../shared/telemetry'; import { KibanaContext, IKibanaContext } from '../../../index'; import { getSourcePath } from '../../routes'; -import { IAppServerData } from './overview'; +import { OverviewLogic, IOverviewValues } from './overview_logic'; import './recent_activity.scss'; @@ -29,10 +30,12 @@ export interface IFeedActivity { sourceId: string; } -export const RecentActivity: React.FC = ({ - organization: { name, defaultOrgName }, - activityFeed, -}) => { +export const RecentActivity: React.FC = () => { + const { + organization: { name, defaultOrgName }, + activityFeed, + } = useValues(OverviewLogic) as IOverviewValues; + return ( , testSubj: 'contentSection', - className: 'test', }; describe('ContentSection', () => { it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.prop('data-test-subj')).toEqual('contentSection'); expect(wrapper.prop('className')).toEqual('test'); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/view_content_header/view_content_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/view_content_header/view_content_header.test.tsx index 4680f15771caa..b0b07c46b4ea8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/view_content_header/view_content_header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/view_content_header/view_content_header.test.tsx @@ -26,9 +26,10 @@ describe('ViewContentHeader', () => { }); it('shows description, when present', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('p').text()).toEqual('Hello World'); + expect(wrapper.find(EuiFlexGroup).prop('alignItems')).toEqual('center'); }); it('shows action, when present', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index 36b1a56ecba26..cfa70ea29eca8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -6,6 +6,13 @@ import React, { useContext } from 'react'; import { Route, Redirect } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { Store } from 'redux'; +import { getContext, resetContext } from 'kea'; + +resetContext({ createStore: true }); + +const store = getContext().store as Store; import { KibanaContext, IKibanaContext } from '../index'; @@ -17,13 +24,13 @@ import { Overview } from './components/overview'; export const WorkplaceSearch: React.FC = () => { const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext; return ( - <> + {!enterpriseSearchUrl ? : } - + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts index b448c59c52f3e..77c35adef3300 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts @@ -13,4 +13,15 @@ export interface IAccount { supportEligible: boolean; } +export interface IOrganization { + name: string; + defaultOrgName: string; +} +export interface IUser { + firstName: string; + email: string; + name: string; + color: string; +} + export type TSpacerSize = 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl'; diff --git a/yarn.lock b/yarn.lock index 0638a019a9402..899bc45fbe3fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19957,6 +19957,11 @@ kdbush@^3.0.0: resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== +kea@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/kea/-/kea-2.1.1.tgz#6e3c65c4873b67d270a2ec7bf73b0d178937234c" + integrity sha512-W9o4lHLOcEDIu3ASHPrWJJJzL1bMkGyxaHn9kuaDgI96ztBShVrf52R0QPGlQ2k9ca3XnkB/dnVHio1UB8kGWA== + kew@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/kew/-/kew-0.1.7.tgz#0a32a817ff1a9b3b12b8c9bacf4bc4d679af8e72" From 88aebc9fe17fa0583b7c5b9af17520511c9b18ad Mon Sep 17 00:00:00 2001 From: liza-mae Date: Mon, 27 Jul 2020 15:10:33 -0600 Subject: [PATCH 05/75] Remove ca cert path for cloud testing (#73317) --- test/common/services/elasticsearch.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/test/common/services/elasticsearch.ts b/test/common/services/elasticsearch.ts index 0436dc901292d..a01f9ff3c8da5 100644 --- a/test/common/services/elasticsearch.ts +++ b/test/common/services/elasticsearch.ts @@ -27,11 +27,18 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function ElasticsearchProvider({ getService }: FtrProviderContext) { const config = getService('config'); - return new Client({ - ssl: { - ca: fs.readFileSync(CA_CERT_PATH, 'utf-8'), - }, - nodes: [formatUrl(config.get('servers.elasticsearch'))], - requestTimeout: config.get('timeouts.esRequestTimeout'), - }); + if (process.env.TEST_CLOUD) { + return new Client({ + nodes: [formatUrl(config.get('servers.elasticsearch'))], + requestTimeout: config.get('timeouts.esRequestTimeout'), + }); + } else { + return new Client({ + ssl: { + ca: fs.readFileSync(CA_CERT_PATH, 'utf-8'), + }, + nodes: [formatUrl(config.get('servers.elasticsearch'))], + requestTimeout: config.get('timeouts.esRequestTimeout'), + }); + } } From 5a472189715931012096b99b95651ffd5791179c Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 27 Jul 2020 16:24:45 -0500 Subject: [PATCH 06/75] [APM] Fix focus map link on service map (#73338) The link was including `serviceName` from the `urlParams` so it was linking to the wrong service. Overwrite the service name so the link is correct. Fixes #72911. --- .../app/ServiceMap/Popover/Buttons.test.tsx | 32 +++++++++++++++++++ .../app/ServiceMap/Popover/Buttons.tsx | 7 +++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx new file mode 100644 index 0000000000000..4146266b17916 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Buttons } from './Buttons'; +import { render } from '@testing-library/react'; + +describe('Popover Buttons', () => { + it('renders', () => { + expect(() => + render() + ).not.toThrowError(); + }); + + it('handles focus click', async () => { + const onFocusClick = jest.fn(); + const result = render( + + ); + const focusButton = await result.findByText('Focus map'); + + focusButton.click(); + + expect(onFocusClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx index d67447e04ef81..cb33fb32f3b0d 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx @@ -22,7 +22,12 @@ export function Buttons({ onFocusClick = () => {}, selectedNodeServiceName, }: ButtonsProps) { - const urlParams = useUrlParams().urlParams as APMQueryParams; + // The params may contain the service name. We want to use the selected node's + // service name in the button URLs, so make a copy and set the + // `serviceName` property. + const urlParams = { ...useUrlParams().urlParams } as APMQueryParams; + urlParams.serviceName = selectedNodeServiceName; + const detailsUrl = getAPMHref( `/services/${selectedNodeServiceName}/transactions`, '', From 157fb097a9aeed8a9e167efa91347617a258ca5b Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 27 Jul 2020 14:31:02 -0700 Subject: [PATCH 07/75] [dev/build/docker_generator] convert to typescript (#73339) Co-authored-by: spalger --- ...e_dockerfiles.js => bundle_dockerfiles.ts} | 28 ++++++++-------- .../os_packages/docker_generator/index.ts | 1 - .../docker_generator/{run.js => run.ts} | 19 ++++++++--- .../docker_generator/template_context.ts | 33 +++++++++++++++++++ ...emplate.js => build_docker_sh.template.ts} | 4 ++- ...ile.template.js => dockerfile.template.ts} | 4 ++- .../templates/{index.js => index.ts} | 0 ...yml.template.js => kibana_yml.template.ts} | 4 ++- 8 files changed, 71 insertions(+), 22 deletions(-) rename src/dev/build/tasks/os_packages/docker_generator/{bundle_dockerfiles.js => bundle_dockerfiles.ts} (80%) rename src/dev/build/tasks/os_packages/docker_generator/{run.js => run.ts} (90%) create mode 100644 src/dev/build/tasks/os_packages/docker_generator/template_context.ts rename src/dev/build/tasks/os_packages/docker_generator/templates/{build_docker_sh.template.js => build_docker_sh.template.ts} (94%) rename src/dev/build/tasks/os_packages/docker_generator/templates/{dockerfile.template.js => dockerfile.template.ts} (98%) rename src/dev/build/tasks/os_packages/docker_generator/templates/{index.js => index.ts} (100%) rename src/dev/build/tasks/os_packages/docker_generator/templates/{kibana_yml.template.js => kibana_yml.template.ts} (91%) diff --git a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.js b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts similarity index 80% rename from src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.js rename to src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts index 3f34a84057668..7a8f7316913be 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.js +++ b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts @@ -18,10 +18,14 @@ */ import { resolve } from 'path'; -import { compressTar, copyAll, mkdirp, write } from '../../../lib'; + +import { ToolingLog } from '@kbn/dev-utils'; + +import { compressTar, copyAll, mkdirp, write, Config } from '../../../lib'; import { dockerfileTemplate } from './templates'; +import { TemplateContext } from './template_context'; -export async function bundleDockerFiles(config, log, build, scope) { +export async function bundleDockerFiles(config: Config, log: ToolingLog, scope: TemplateContext) { log.info( `Generating kibana${scope.imageFlavor}${scope.ubiImageFlavor} docker build context bundle` ); @@ -50,17 +54,15 @@ export async function bundleDockerFiles(config, log, build, scope) { // Compress dockerfiles dir created inside // docker build dir as output it as a target // on targets folder - await compressTar( - { - archiverOptions: { - gzip: true, - gzipOptions: { - level: 9, - }, + await compressTar({ + source: dockerFilesBuildDir, + destination: dockerFilesOutputDir, + archiverOptions: { + gzip: true, + gzipOptions: { + level: 9, }, - createRootDirectory: false, }, - dockerFilesBuildDir, - dockerFilesOutputDir - ); + createRootDirectory: false, + }); } diff --git a/src/dev/build/tasks/os_packages/docker_generator/index.ts b/src/dev/build/tasks/os_packages/docker_generator/index.ts index 78d2b197dc7b2..dff56585fc704 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/index.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/index.ts @@ -17,5 +17,4 @@ * under the License. */ -// @ts-expect-error not ts yet export { runDockerGenerator, runDockerGeneratorForUBI } from './run'; diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.js b/src/dev/build/tasks/os_packages/docker_generator/run.ts similarity index 90% rename from src/dev/build/tasks/os_packages/docker_generator/run.js rename to src/dev/build/tasks/os_packages/docker_generator/run.ts index b6dab43887f14..0a26729f3502d 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.js +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -20,8 +20,12 @@ import { access, link, unlink, chmod } from 'fs'; import { resolve } from 'path'; import { promisify } from 'util'; -import { write, copyAll, mkdirp, exec } from '../../../lib'; + +import { ToolingLog } from '@kbn/dev-utils'; + +import { write, copyAll, mkdirp, exec, Config, Build } from '../../../lib'; import * as dockerTemplates from './templates'; +import { TemplateContext } from './template_context'; import { bundleDockerFiles } from './bundle_dockerfiles'; const accessAsync = promisify(access); @@ -29,7 +33,12 @@ const linkAsync = promisify(link); const unlinkAsync = promisify(unlink); const chmodAsync = promisify(chmod); -export async function runDockerGenerator(config, log, build, ubi = false) { +export async function runDockerGenerator( + config: Config, + log: ToolingLog, + build: Build, + ubi: boolean = false +) { // UBI var config const baseOSImage = ubi ? 'registry.access.redhat.com/ubi7/ubi-minimal:7.7' : 'centos:7'; const ubiVersionTag = 'ubi7'; @@ -52,7 +61,7 @@ export async function runDockerGenerator(config, log, build, ubi = false) { const dockerOutputDir = config.resolveFromTarget( `kibana${imageFlavor}${ubiImageFlavor}-${versionTag}-docker.tar.gz` ); - const scope = { + const scope: TemplateContext = { artifactTarball, imageFlavor, versionTag, @@ -112,10 +121,10 @@ export async function runDockerGenerator(config, log, build, ubi = false) { }); // Pack Dockerfiles and create a target for them - await bundleDockerFiles(config, log, build, scope); + await bundleDockerFiles(config, log, scope); } -export async function runDockerGeneratorForUBI(config, log, build) { +export async function runDockerGeneratorForUBI(config: Config, log: ToolingLog, build: Build) { // Only run ubi docker image build for default distribution if (build.isOss()) { return; diff --git a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts new file mode 100644 index 0000000000000..115d4c6927c30 --- /dev/null +++ b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface TemplateContext { + artifactTarball: string; + imageFlavor: string; + versionTag: string; + license: string; + artifactsDir: string; + imageTag: string; + dockerBuildDir: string; + dockerOutputDir: string; + baseOSImage: string; + ubiImageFlavor: string; + dockerBuildDate: string; + usePublicArtifact?: boolean; +} diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.js b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts similarity index 94% rename from src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.js rename to src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts index 4e8dfe188b867..ff6fcf7548d9d 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.js +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts @@ -19,6 +19,8 @@ import dedent from 'dedent'; +import { TemplateContext } from '../template_context'; + function generator({ imageTag, imageFlavor, @@ -26,7 +28,7 @@ function generator({ dockerOutputDir, baseOSImage, ubiImageFlavor, -}) { +}: TemplateContext) { return dedent(` #!/usr/bin/env bash # diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts similarity index 98% rename from src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js rename to src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts index 5832d00162b20..ea2f881768c8f 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts @@ -19,6 +19,8 @@ import dedent from 'dedent'; +import { TemplateContext } from '../template_context'; + function generator({ artifactTarball, versionTag, @@ -27,7 +29,7 @@ function generator({ baseOSImage, ubiImageFlavor, dockerBuildDate, -}) { +}: TemplateContext) { const copyArtifactTarballInsideDockerOptFolder = () => { if (usePublicArtifact) { return `RUN cd /opt && curl --retry 8 -s -L -O https://artifacts.elastic.co/downloads/kibana/${artifactTarball} && cd -`; diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/index.js b/src/dev/build/tasks/os_packages/docker_generator/templates/index.ts similarity index 100% rename from src/dev/build/tasks/os_packages/docker_generator/templates/index.js rename to src/dev/build/tasks/os_packages/docker_generator/templates/index.ts diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js b/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts similarity index 91% rename from src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js rename to src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts index c80f9334cfaeb..240ec6f4e9326 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts @@ -19,7 +19,9 @@ import dedent from 'dedent'; -function generator({ imageFlavor }) { +import { TemplateContext } from '../template_context'; + +function generator({ imageFlavor }: TemplateContext) { return dedent(` # # ** THIS IS AN AUTO-GENERATED FILE ** From 57997beed8f7eaf7f67cd17d397eb4abcd6abf36 Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 27 Jul 2020 15:06:42 -0700 Subject: [PATCH 08/75] [Enterprise Search] Error state UI tweaks to account for current Cloud SSO behavior (#73324) * Do not disable the Launch App Search button on the error page - so users always have access to App Search * Add troubleshooting steps that mention user authentication - more info can be found in setup guide * Tweak styling/spacing on troubleshooting steps * Copyedits (thanks Chris!) --- .../components/empty_states/error_state.tsx | 2 +- .../engine_overview_header.test.tsx | 8 ------- .../engine_overview_header.tsx | 23 +++++------------- .../error_state/error_state_prompt.scss | 12 ++++++++++ .../shared/error_state/error_state_prompt.tsx | 24 ++++++++++++++++++- 5 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.scss diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx index 7ac02082ee75c..346e70d32f7b1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx @@ -21,7 +21,7 @@ export const ErrorState: React.FC = () => { - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx index 2e49540270ef0..7d2106f2a56f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx @@ -30,12 +30,4 @@ describe('EngineOverviewHeader', () => { button.simulate('click'); expect(sendTelemetry).toHaveBeenCalled(); }); - - it('renders a disabled button when isButtonDisabled is true', () => { - const wrapper = shallow(); - const button = wrapper.find('[data-test-subj="launchButton"]'); - - expect(button.prop('isDisabled')).toBe(true); - expect(button.prop('href')).toBeUndefined(); - }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx index 9aafa8ec0380c..cc480d241ad50 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx @@ -18,34 +18,23 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { sendTelemetry } from '../../../shared/telemetry'; import { KibanaContext, IKibanaContext } from '../../../index'; -interface IEngineOverviewHeaderProps { - isButtonDisabled?: boolean; -} - -export const EngineOverviewHeader: React.FC = ({ - isButtonDisabled, -}) => { +export const EngineOverviewHeader: React.FC = () => { const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext; const buttonProps = { fill: true, iconType: 'popout', 'data-test-subj': 'launchButton', - } as EuiButtonProps & EuiLinkProps; - - if (isButtonDisabled) { - buttonProps.isDisabled = true; - } else { - buttonProps.href = `${enterpriseSearchUrl}/as`; - buttonProps.target = '_blank'; - buttonProps.onClick = () => + href: `${enterpriseSearchUrl}/as`, + target: '_blank', + onClick: () => sendTelemetry({ http, product: 'app_search', action: 'clicked', metric: 'header_launch_button', - }); - } + }), + } as EuiButtonProps & EuiLinkProps; return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.scss b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.scss new file mode 100644 index 0000000000000..0d9926ab147bf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.scss @@ -0,0 +1,12 @@ +.troubleshootingSteps { + text-align: left; + + li { + margin: $euiSizeS auto; + } + + ul, + ol { + margin-bottom: 0; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx index 81455cea0b497..ccd5beff66e70 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx @@ -11,6 +11,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton } from '../react_router_helpers'; import { KibanaContext, IKibanaContext } from '../../index'; +import './error_state_prompt.scss'; + export const ErrorStatePrompt: React.FC = () => { const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext; @@ -38,7 +40,7 @@ export const ErrorStatePrompt: React.FC = () => { }} />

-
    +
    1. { defaultMessage="Confirm that the Enterprise Search server is responsive." />
    2. +
    3. + +
        +
      • + +
      • +
      • + +
      • +
      +
    4. Date: Mon, 27 Jul 2020 18:19:16 -0400 Subject: [PATCH 09/75] [Security Solution][Exceptions] - Update exception item comments to include id (#73129) ## Summary This PR is somewhat of an intermediary step. Comments on exception list items are denormalized. We initially decided that we would not add `uuid` to comments, but found that it is in fact necessary. This is intermediary in the sense that what we ideally want to have is a dedicated `comments` CRUD route. Also just note that I added a callout for when a version conflict occurs (ie: exception item was updated by someone else while a user is editing the same item). With this PR users are able to: - Create comments when creating exception list items - Add new comments on exception item update Users will currently be blocked from: - Deleting comments - Updating comments - Updating exception item if version conflict is found --- x-pack/plugins/lists/common/constants.mock.ts | 1 + .../create_endpoint_list_item_schema.test.ts | 36 +- .../create_exception_list_item_schema.test.ts | 36 +- ...ate_exception_list_item_validation.test.ts | 43 ++ .../update_exception_list_item_validation.ts | 40 ++ .../{comments.mock.ts => comment.mock.ts} | 7 +- .../{comments.test.ts => comment.test.ts} | 109 +++-- .../schemas/types/{comments.ts => comment.ts} | 23 +- ...omments.mock.ts => create_comment.mock.ts} | 4 +- ...omments.test.ts => create_comment.test.ts} | 50 +-- .../{create_comments.ts => create_comment.ts} | 11 +- .../types/default_comments_array.test.ts | 21 +- .../schemas/types/default_comments_array.ts | 6 +- .../default_create_comments_array.test.ts | 30 +- .../types/default_create_comments_array.ts | 6 +- .../default_update_comments_array.test.ts | 23 +- .../types/default_update_comments_array.ts | 2 +- .../lists/common/schemas/types/index.ts | 6 +- ...omments.mock.ts => update_comment.mock.ts} | 15 +- .../schemas/types/update_comment.test.ts | 150 +++++++ .../{update_comments.ts => update_comment.ts} | 20 +- .../schemas/types/update_comments.test.ts | 108 ----- x-pack/plugins/lists/common/shared_exports.ts | 5 +- .../update_exception_list_item_route.ts | 6 + .../server/saved_objects/exception_list.ts | 3 + .../updates/simple_update_item.json | 25 +- .../create_exception_list_item.ts | 5 +- .../services/exception_lists/utils.test.ts | 390 ++---------------- .../server/services/exception_lists/utils.ts | 115 +----- .../common/shared_imports.ts | 5 +- .../exceptions/add_exception_comments.tsx | 4 +- .../exceptions/add_exception_modal/index.tsx | 4 +- .../components/exceptions/builder/index.tsx | 2 +- .../exceptions/edit_exception_modal/index.tsx | 40 +- .../edit_exception_modal/translations.ts | 15 + .../components/exceptions/helpers.test.tsx | 55 ++- .../common/components/exceptions/helpers.tsx | 55 ++- .../exception_item/exception_details.test.tsx | 2 +- .../viewer/exception_item/index.stories.tsx | 2 +- .../viewer/exception_item/index.test.tsx | 2 +- .../components/exceptions/viewer/index.tsx | 3 +- 41 files changed, 702 insertions(+), 783 deletions(-) create mode 100644 x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.test.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.ts rename x-pack/plugins/lists/common/schemas/types/{comments.mock.ts => comment.mock.ts} (71%) rename x-pack/plugins/lists/common/schemas/types/{comments.test.ts => comment.test.ts} (56%) rename x-pack/plugins/lists/common/schemas/types/{comments.ts => comment.ts} (56%) rename x-pack/plugins/lists/common/schemas/types/{create_comments.mock.ts => create_comment.mock.ts} (73%) rename x-pack/plugins/lists/common/schemas/types/{create_comments.test.ts => create_comment.test.ts} (72%) rename x-pack/plugins/lists/common/schemas/types/{create_comments.ts => create_comment.ts} (64%) rename x-pack/plugins/lists/common/schemas/types/{update_comments.mock.ts => update_comment.mock.ts} (54%) create mode 100644 x-pack/plugins/lists/common/schemas/types/update_comment.test.ts rename x-pack/plugins/lists/common/schemas/types/{update_comments.ts => update_comment.ts} (58%) delete mode 100644 x-pack/plugins/lists/common/schemas/types/update_comments.test.ts diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts index 30f219c3ec101..22706890e2020 100644 --- a/x-pack/plugins/lists/common/constants.mock.ts +++ b/x-pack/plugins/lists/common/constants.mock.ts @@ -6,6 +6,7 @@ import { EntriesArray } from './schemas/types'; export const DATE_NOW = '2020-04-20T15:25:31.830Z'; +export const OLD_DATE_RELATIVE_TO_DATE_NOW = '2020-04-19T15:25:31.830Z'; export const USER = 'some user'; export const LIST_INDEX = '.lists'; export const LIST_ITEM_INDEX = '.items'; diff --git a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts index 5de9fbb0d5b50..75e0410be610a 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts @@ -8,8 +8,8 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; -import { getCreateCommentsArrayMock } from '../types/create_comments.mock'; -import { getCommentsMock } from '../types/comments.mock'; +import { getCreateCommentsArrayMock } from '../types/create_comment.mock'; +import { getCommentsMock } from '../types/comment.mock'; import { CommentsArray } from '../types'; import { @@ -19,7 +19,7 @@ import { import { getCreateEndpointListItemSchemaMock } from './create_endpoint_list_item_schema.mock'; describe('create_endpoint_list_item_schema', () => { - test('it should validate a typical list item request not counting the auto generated uuid', () => { + test('it should pass validation when supplied a typical list item request not counting the auto generated uuid', () => { const payload = getCreateEndpointListItemSchemaMock(); const decoded = createEndpointListItemSchema.decode(payload); const checked = exactCheck(payload, decoded); @@ -29,7 +29,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate an undefined for "description"', () => { + test('it should fail validation when supplied an undefined for "description"', () => { const payload = getCreateEndpointListItemSchemaMock(); delete payload.description; const decoded = createEndpointListItemSchema.decode(payload); @@ -41,7 +41,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate an undefined for "name"', () => { + test('it should fail validation when supplied an undefined for "name"', () => { const payload = getCreateEndpointListItemSchemaMock(); delete payload.name; const decoded = createEndpointListItemSchema.decode(payload); @@ -53,7 +53,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate an undefined for "type"', () => { + test('it should fail validation when supplied an undefined for "type"', () => { const payload = getCreateEndpointListItemSchemaMock(); delete payload.type; const decoded = createEndpointListItemSchema.decode(payload); @@ -65,7 +65,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate a "list_id" since it does not required one', () => { + test('it should fail validation when supplied a "list_id" since it does not required one', () => { const inputPayload: CreateEndpointListItemSchema & { list_id: string } = { ...getCreateEndpointListItemSchemaMock(), list_id: 'list-123', @@ -77,7 +77,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate a "namespace_type" since it does not required one', () => { + test('it should fail validation when supplied a "namespace_type" since it does not required one', () => { const inputPayload: CreateEndpointListItemSchema & { namespace_type: string } = { ...getCreateEndpointListItemSchemaMock(), namespace_type: 'single', @@ -89,7 +89,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should validate an undefined for "meta" but strip it out and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "meta" but strip it out and generate a correct body not counting the auto generated uuid', () => { const payload = getCreateEndpointListItemSchemaMock(); const outputPayload = getCreateEndpointListItemSchemaMock(); delete payload.meta; @@ -102,7 +102,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "comments" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "comments" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); const outputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload.comments; @@ -115,7 +115,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate "comments" array', () => { + test('it should pass validation when supplied "comments" array', () => { const inputPayload = { ...getCreateEndpointListItemSchemaMock(), comments: getCreateCommentsArrayMock(), @@ -128,7 +128,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(inputPayload); }); - test('it should NOT validate "comments" with "created_at" or "created_by" values', () => { + test('it should fail validation when supplied "comments" with "created_at", "created_by", or "id" values', () => { const inputPayload: Omit & { comments?: CommentsArray; } = { @@ -138,11 +138,11 @@ describe('create_endpoint_list_item_schema', () => { const decoded = createEndpointListItemSchema.decode(inputPayload); const checked = exactCheck(inputPayload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "created_at,created_by"']); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "created_at,created_by,id"']); expect(message.schema).toEqual({}); }); - test('it should NOT validate an undefined for "entries"', () => { + test('it should fail validation when supplied an undefined for "entries"', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); const outputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload.entries; @@ -157,7 +157,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should validate an undefined for "tags" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "tags" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); const outputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload.tags; @@ -170,7 +170,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "_tags" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "_tags" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); const outputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload._tags; @@ -183,7 +183,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "item_id" and auto generate a uuid', () => { + test('it should pass validation when supplied an undefined for "item_id" and auto generate a uuid', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload.item_id; const decoded = createEndpointListItemSchema.decode(inputPayload); @@ -195,7 +195,7 @@ describe('create_endpoint_list_item_schema', () => { ); }); - test('it should validate an undefined for "item_id" and generate a correct body not counting the uuid', () => { + test('it should pass validation when supplied an undefined for "item_id" and generate a correct body not counting the uuid', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload.item_id; const decoded = createEndpointListItemSchema.decode(inputPayload); diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts index 08f3966af08d9..cf4c1fea0306f 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts @@ -8,8 +8,8 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; -import { getCreateCommentsArrayMock } from '../types/create_comments.mock'; -import { getCommentsMock } from '../types/comments.mock'; +import { getCreateCommentsArrayMock } from '../types/create_comment.mock'; +import { getCommentsMock } from '../types/comment.mock'; import { CommentsArray } from '../types'; import { @@ -19,7 +19,7 @@ import { import { getCreateExceptionListItemSchemaMock } from './create_exception_list_item_schema.mock'; describe('create_exception_list_item_schema', () => { - test('it should validate a typical exception list item request not counting the auto generated uuid', () => { + test('it should pass validation when supplied a typical exception list item request not counting the auto generated uuid', () => { const payload = getCreateExceptionListItemSchemaMock(); const decoded = createExceptionListItemSchema.decode(payload); const checked = exactCheck(payload, decoded); @@ -29,7 +29,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate an undefined for "description"', () => { + test('it should fail validation when supplied an undefined for "description"', () => { const payload = getCreateExceptionListItemSchemaMock(); delete payload.description; const decoded = createExceptionListItemSchema.decode(payload); @@ -41,7 +41,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate an undefined for "name"', () => { + test('it should fail validation when supplied an undefined for "name"', () => { const payload = getCreateExceptionListItemSchemaMock(); delete payload.name; const decoded = createExceptionListItemSchema.decode(payload); @@ -53,7 +53,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate an undefined for "type"', () => { + test('it should fail validation when supplied an undefined for "type"', () => { const payload = getCreateExceptionListItemSchemaMock(); delete payload.type; const decoded = createExceptionListItemSchema.decode(payload); @@ -65,7 +65,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate an undefined for "list_id"', () => { + test('it should fail validation when supplied an undefined for "list_id"', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.list_id; const decoded = createExceptionListItemSchema.decode(inputPayload); @@ -77,7 +77,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should validate an undefined for "meta" but strip it out and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "meta" but strip it out and generate a correct body not counting the auto generated uuid', () => { const payload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete payload.meta; @@ -90,7 +90,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "comments" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "comments" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.comments; @@ -103,7 +103,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate "comments" array', () => { + test('it should pass validation when supplied "comments" array', () => { const inputPayload = { ...getCreateExceptionListItemSchemaMock(), comments: getCreateCommentsArrayMock(), @@ -116,7 +116,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(inputPayload); }); - test('it should NOT validate "comments" with "created_at" or "created_by" values', () => { + test('it should fail validation when supplied "comments" with "created_at" or "created_by" values', () => { const inputPayload: Omit & { comments?: CommentsArray; } = { @@ -126,11 +126,11 @@ describe('create_exception_list_item_schema', () => { const decoded = createExceptionListItemSchema.decode(inputPayload); const checked = exactCheck(inputPayload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "created_at,created_by"']); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "created_at,created_by,id"']); expect(message.schema).toEqual({}); }); - test('it should NOT validate an undefined for "entries"', () => { + test('it should fail validation when supplied an undefined for "entries"', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.entries; @@ -145,7 +145,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should validate an undefined for "namespace_type" but return enum "single" and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "namespace_type" but return enum "single" and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.namespace_type; @@ -158,7 +158,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "tags" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "tags" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.tags; @@ -171,7 +171,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "_tags" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "_tags" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload._tags; @@ -184,7 +184,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "item_id" and auto generate a uuid', () => { + test('it should pass validation when supplied an undefined for "item_id" and auto generate a uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.item_id; const decoded = createExceptionListItemSchema.decode(inputPayload); @@ -196,7 +196,7 @@ describe('create_exception_list_item_schema', () => { ); }); - test('it should validate an undefined for "item_id" and generate a correct body not counting the uuid', () => { + test('it should pass validation when supplied an undefined for "item_id" and generate a correct body not counting the uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.item_id; const decoded = createExceptionListItemSchema.decode(inputPayload); diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.test.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.test.ts new file mode 100644 index 0000000000000..3358582786cc7 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getUpdateExceptionListItemSchemaMock } from './update_exception_list_item_schema.mock'; +import { validateComments } from './update_exception_list_item_validation'; + +describe('update_exception_list_item_validation', () => { + describe('#validateComments', () => { + test('it returns no errors if comments is undefined', () => { + const payload = getUpdateExceptionListItemSchemaMock(); + delete payload.comments; + const output = validateComments(payload); + + expect(output).toEqual([]); + }); + + test('it returns no errors if new comments are append only', () => { + const payload = getUpdateExceptionListItemSchemaMock(); + payload.comments = [ + { comment: 'Im an old comment', id: '1' }, + { comment: 'Im a new comment' }, + ]; + const output = validateComments(payload); + + expect(output).toEqual([]); + }); + + test('it returns error if comments are not append only', () => { + const payload = getUpdateExceptionListItemSchemaMock(); + payload.comments = [ + { comment: 'Im an old comment', id: '1' }, + { comment: 'Im a new comment modifying the order of existing comments' }, + { comment: 'Im an old comment', id: '2' }, + ]; + const output = validateComments(payload); + + expect(output).toEqual(['item "comments" are append only']); + }); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.ts new file mode 100644 index 0000000000000..5e44c4e9f73e7 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UpdateExceptionListItemSchema } from './update_exception_list_item_schema'; + +export const validateComments = (item: UpdateExceptionListItemSchema): string[] => { + if (item.comments == null) { + return []; + } + + const [appendOnly] = item.comments.reduce( + (acc, comment) => { + const [, hasNewComments] = acc; + if (comment.id == null) { + return [true, true]; + } + + if (hasNewComments && comment.id != null) { + return [false, true]; + } + + return acc; + }, + [true, false] + ); + if (!appendOnly) { + return ['item "comments" are append only']; + } else { + return []; + } +}; + +export const updateExceptionListItemValidate = ( + schema: UpdateExceptionListItemSchema +): string[] => { + return [...validateComments(schema)]; +}; diff --git a/x-pack/plugins/lists/common/schemas/types/comments.mock.ts b/x-pack/plugins/lists/common/schemas/types/comment.mock.ts similarity index 71% rename from x-pack/plugins/lists/common/schemas/types/comments.mock.ts rename to x-pack/plugins/lists/common/schemas/types/comment.mock.ts index 9e56ac292f8b5..213259b3cce29 100644 --- a/x-pack/plugins/lists/common/schemas/types/comments.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/comment.mock.ts @@ -4,14 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DATE_NOW, USER } from '../../constants.mock'; +import { DATE_NOW, ID, USER } from '../../constants.mock'; -import { Comments, CommentsArray } from './comments'; +import { Comment, CommentsArray } from './comment'; -export const getCommentsMock = (): Comments => ({ +export const getCommentsMock = (): Comment => ({ comment: 'some old comment', created_at: DATE_NOW, created_by: USER, + id: ID, }); export const getCommentsArrayMock = (): CommentsArray => [getCommentsMock(), getCommentsMock()]; diff --git a/x-pack/plugins/lists/common/schemas/types/comments.test.ts b/x-pack/plugins/lists/common/schemas/types/comment.test.ts similarity index 56% rename from x-pack/plugins/lists/common/schemas/types/comments.test.ts rename to x-pack/plugins/lists/common/schemas/types/comment.test.ts index 29bfde03abcc8..c7c945277f756 100644 --- a/x-pack/plugins/lists/common/schemas/types/comments.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/comment.test.ts @@ -10,56 +10,79 @@ import { left } from 'fp-ts/lib/Either'; import { DATE_NOW } from '../../constants.mock'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; -import { getCommentsArrayMock, getCommentsMock } from './comments.mock'; +import { getCommentsArrayMock, getCommentsMock } from './comment.mock'; import { - Comments, + Comment, CommentsArray, CommentsArrayOrUndefined, - comments, + comment, commentsArray, commentsArrayOrUndefined, -} from './comments'; +} from './comment'; -describe('Comments', () => { - describe('comments', () => { - test('it should validate a comments', () => { +describe('Comment', () => { + describe('comment', () => { + test('it fails validation when "id" is undefined', () => { + const payload = { ...getCommentsMock(), id: undefined }; + const decoded = comment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it passes validation with a typical comment', () => { const payload = getCommentsMock(); - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(payload); }); - test('it should validate with "updated_at" and "updated_by"', () => { + test('it passes validation with "updated_at" and "updated_by" fields included', () => { const payload = getCommentsMock(); payload.updated_at = DATE_NOW; payload.updated_by = 'someone'; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(payload); }); - test('it should not validate when undefined', () => { + test('it fails validation when undefined', () => { const payload = undefined; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)"', - 'Invalid value "undefined" supplied to "({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)"', + 'Invalid value "undefined" supplied to "({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)"', + 'Invalid value "undefined" supplied to "({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)"', ]); expect(message.schema).toEqual({}); }); - test('it should not validate when "comment" is not a string', () => { - const payload: Omit & { comment: string[] } = { + test('it fails validation when "comment" is an empty string', () => { + const payload: Omit & { comment: string } = { + ...getCommentsMock(), + comment: '', + }; + const decoded = comment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "comment"']); + expect(message.schema).toEqual({}); + }); + + test('it fails validation when "comment" is not a string', () => { + const payload: Omit & { comment: string[] } = { ...getCommentsMock(), comment: ['some value'], }; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -68,12 +91,12 @@ describe('Comments', () => { expect(message.schema).toEqual({}); }); - test('it should not validate when "created_at" is not a string', () => { - const payload: Omit & { created_at: number } = { + test('it fails validation when "created_at" is not a string', () => { + const payload: Omit & { created_at: number } = { ...getCommentsMock(), created_at: 1, }; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -82,12 +105,12 @@ describe('Comments', () => { expect(message.schema).toEqual({}); }); - test('it should not validate when "created_by" is not a string', () => { - const payload: Omit & { created_by: number } = { + test('it fails validation when "created_by" is not a string', () => { + const payload: Omit & { created_by: number } = { ...getCommentsMock(), created_by: 1, }; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -96,12 +119,12 @@ describe('Comments', () => { expect(message.schema).toEqual({}); }); - test('it should not validate when "updated_at" is not a string', () => { - const payload: Omit & { updated_at: number } = { + test('it fails validation when "updated_at" is not a string', () => { + const payload: Omit & { updated_at: number } = { ...getCommentsMock(), updated_at: 1, }; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -110,12 +133,12 @@ describe('Comments', () => { expect(message.schema).toEqual({}); }); - test('it should not validate when "updated_by" is not a string', () => { - const payload: Omit & { updated_by: number } = { + test('it fails validation when "updated_by" is not a string', () => { + const payload: Omit & { updated_by: number } = { ...getCommentsMock(), updated_by: 1, }; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -125,11 +148,11 @@ describe('Comments', () => { }); test('it should strip out extra keys', () => { - const payload: Comments & { + const payload: Comment & { extraKey?: string; } = getCommentsMock(); payload.extraKey = 'some value'; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -138,7 +161,7 @@ describe('Comments', () => { }); describe('commentsArray', () => { - test('it should validate an array of comments', () => { + test('it passes validation an array of Comment', () => { const payload = getCommentsArrayMock(); const decoded = commentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -147,7 +170,7 @@ describe('Comments', () => { expect(message.schema).toEqual(payload); }); - test('it should validate when a comments includes "updated_at" and "updated_by"', () => { + test('it passes validation when a Comment includes "updated_at" and "updated_by"', () => { const commentsPayload = getCommentsMock(); commentsPayload.updated_at = DATE_NOW; commentsPayload.updated_by = 'someone'; @@ -159,32 +182,32 @@ describe('Comments', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate when undefined', () => { + test('it fails validation when undefined', () => { const payload = undefined; const decoded = commentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "undefined" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', ]); expect(message.schema).toEqual({}); }); - test('it should not validate when array includes non comments types', () => { + test('it fails validation when array includes non Comment types', () => { const payload = ([1] as unknown) as CommentsArray; const decoded = commentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', ]); expect(message.schema).toEqual({}); }); }); describe('commentsArrayOrUndefined', () => { - test('it should validate an array of comments', () => { + test('it passes validation an array of Comment', () => { const payload = getCommentsArrayMock(); const decoded = commentsArrayOrUndefined.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -193,7 +216,7 @@ describe('Comments', () => { expect(message.schema).toEqual(payload); }); - test('it should validate when undefined', () => { + test('it passes validation when undefined', () => { const payload = undefined; const decoded = commentsArrayOrUndefined.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -202,14 +225,14 @@ describe('Comments', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate when array includes non comments types', () => { + test('it fails validation when array includes non Comment types', () => { const payload = ([1] as unknown) as CommentsArrayOrUndefined; const decoded = commentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/types/comments.ts b/x-pack/plugins/lists/common/schemas/types/comment.ts similarity index 56% rename from x-pack/plugins/lists/common/schemas/types/comments.ts rename to x-pack/plugins/lists/common/schemas/types/comment.ts index 0ee3b05c8102f..6b0b0166b9ee1 100644 --- a/x-pack/plugins/lists/common/schemas/types/comments.ts +++ b/x-pack/plugins/lists/common/schemas/types/comment.ts @@ -3,26 +3,33 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + +/* eslint-disable @typescript-eslint/camelcase */ + import * as t from 'io-ts'; -export const comments = t.intersection([ +import { NonEmptyString } from '../../siem_common_deps'; +import { created_at, created_by, id, updated_at, updated_by } from '../common/schemas'; + +export const comment = t.intersection([ t.exact( t.type({ - comment: t.string, - created_at: t.string, // TODO: Make this into an ISO Date string check, - created_by: t.string, + comment: NonEmptyString, + created_at, + created_by, + id, }) ), t.exact( t.partial({ - updated_at: t.string, - updated_by: t.string, + updated_at, + updated_by, }) ), ]); -export const commentsArray = t.array(comments); +export const commentsArray = t.array(comment); export type CommentsArray = t.TypeOf; -export type Comments = t.TypeOf; +export type Comment = t.TypeOf; export const commentsArrayOrUndefined = t.union([commentsArray, t.undefined]); export type CommentsArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/create_comments.mock.ts b/x-pack/plugins/lists/common/schemas/types/create_comment.mock.ts similarity index 73% rename from x-pack/plugins/lists/common/schemas/types/create_comments.mock.ts rename to x-pack/plugins/lists/common/schemas/types/create_comment.mock.ts index 60a59432275ca..689d4ccdc2c2e 100644 --- a/x-pack/plugins/lists/common/schemas/types/create_comments.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/create_comment.mock.ts @@ -3,9 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CreateComments, CreateCommentsArray } from './create_comments'; +import { CreateComment, CreateCommentsArray } from './create_comment'; -export const getCreateCommentsMock = (): CreateComments => ({ +export const getCreateCommentsMock = (): CreateComment => ({ comment: 'some comments', }); diff --git a/x-pack/plugins/lists/common/schemas/types/create_comments.test.ts b/x-pack/plugins/lists/common/schemas/types/create_comment.test.ts similarity index 72% rename from x-pack/plugins/lists/common/schemas/types/create_comments.test.ts rename to x-pack/plugins/lists/common/schemas/types/create_comment.test.ts index d2680750e05e4..366bf84d48bbf 100644 --- a/x-pack/plugins/lists/common/schemas/types/create_comments.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/create_comment.test.ts @@ -9,44 +9,44 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; -import { getCreateCommentsArrayMock, getCreateCommentsMock } from './create_comments.mock'; +import { getCreateCommentsArrayMock, getCreateCommentsMock } from './create_comment.mock'; import { - CreateComments, + CreateComment, CreateCommentsArray, CreateCommentsArrayOrUndefined, - createComments, + createComment, createCommentsArray, createCommentsArrayOrUndefined, -} from './create_comments'; +} from './create_comment'; -describe('CreateComments', () => { - describe('createComments', () => { - test('it should validate a comments', () => { +describe('CreateComment', () => { + describe('createComment', () => { + test('it passes validation with a default comment', () => { const payload = getCreateCommentsMock(); - const decoded = createComments.decode(payload); + const decoded = createComment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(payload); }); - test('it should not validate when undefined', () => { + test('it fails validation when undefined', () => { const payload = undefined; - const decoded = createComments.decode(payload); + const decoded = createComment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "{| comment: string |}"', + 'Invalid value "undefined" supplied to "{| comment: NonEmptyString |}"', ]); expect(message.schema).toEqual({}); }); - test('it should not validate when "comment" is not a string', () => { - const payload: Omit & { comment: string[] } = { + test('it fails validation when "comment" is not a string', () => { + const payload: Omit & { comment: string[] } = { ...getCreateCommentsMock(), comment: ['some value'], }; - const decoded = createComments.decode(payload); + const decoded = createComment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -56,11 +56,11 @@ describe('CreateComments', () => { }); test('it should strip out extra keys', () => { - const payload: CreateComments & { + const payload: CreateComment & { extraKey?: string; } = getCreateCommentsMock(); payload.extraKey = 'some value'; - const decoded = createComments.decode(payload); + const decoded = createComment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -69,7 +69,7 @@ describe('CreateComments', () => { }); describe('createCommentsArray', () => { - test('it should validate an array of comments', () => { + test('it passes validation an array of comments', () => { const payload = getCreateCommentsArrayMock(); const decoded = createCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -78,31 +78,31 @@ describe('CreateComments', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate when undefined', () => { + test('it fails validation when undefined', () => { const payload = undefined; const decoded = createCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "Array<{| comment: string |}>"', + 'Invalid value "undefined" supplied to "Array<{| comment: NonEmptyString |}>"', ]); expect(message.schema).toEqual({}); }); - test('it should not validate when array includes non comments types', () => { + test('it fails validation when array includes non comments types', () => { const payload = ([1] as unknown) as CreateCommentsArray; const decoded = createCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<{| comment: string |}>"', + 'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"', ]); expect(message.schema).toEqual({}); }); }); describe('createCommentsArrayOrUndefined', () => { - test('it should validate an array of comments', () => { + test('it passes validation an array of comments', () => { const payload = getCreateCommentsArrayMock(); const decoded = createCommentsArrayOrUndefined.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -111,7 +111,7 @@ describe('CreateComments', () => { expect(message.schema).toEqual(payload); }); - test('it should validate when undefined', () => { + test('it passes validation when undefined', () => { const payload = undefined; const decoded = createCommentsArrayOrUndefined.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -120,13 +120,13 @@ describe('CreateComments', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate when array includes non comments types', () => { + test('it fails validation when array includes non comments types', () => { const payload = ([1] as unknown) as CreateCommentsArrayOrUndefined; const decoded = createCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<{| comment: string |}>"', + 'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/types/create_comments.ts b/x-pack/plugins/lists/common/schemas/types/create_comment.ts similarity index 64% rename from x-pack/plugins/lists/common/schemas/types/create_comments.ts rename to x-pack/plugins/lists/common/schemas/types/create_comment.ts index c34419298ef93..fd33313430ce6 100644 --- a/x-pack/plugins/lists/common/schemas/types/create_comments.ts +++ b/x-pack/plugins/lists/common/schemas/types/create_comment.ts @@ -5,14 +5,17 @@ */ import * as t from 'io-ts'; -export const createComments = t.exact( +import { NonEmptyString } from '../../siem_common_deps'; + +export const createComment = t.exact( t.type({ - comment: t.string, + comment: NonEmptyString, }) ); -export const createCommentsArray = t.array(createComments); +export type CreateComment = t.TypeOf; +export const createCommentsArray = t.array(createComment); export type CreateCommentsArray = t.TypeOf; -export type CreateComments = t.TypeOf; +export type CreateComments = t.TypeOf; export const createCommentsArrayOrUndefined = t.union([createCommentsArray, t.undefined]); export type CreateCommentsArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts index 3a4241aaec82d..541b8ab1c799c 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts @@ -10,11 +10,11 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; import { DefaultCommentsArray } from './default_comments_array'; -import { CommentsArray } from './comments'; -import { getCommentsArrayMock } from './comments.mock'; +import { CommentsArray } from './comment'; +import { getCommentsArrayMock } from './comment.mock'; describe('default_comments_array', () => { - test('it should validate an empty array', () => { + test('it should pass validation when supplied an empty array', () => { const payload: CommentsArray = []; const decoded = DefaultCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -23,7 +23,7 @@ describe('default_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should validate an array of comments', () => { + test('it should pass validation when supplied an array of comments', () => { const payload: CommentsArray = getCommentsArrayMock(); const decoded = DefaultCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -32,27 +32,26 @@ describe('default_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should NOT validate an array of numbers', () => { + test('it should fail validation when supplied an array of numbers', () => { const payload = [1]; const decoded = DefaultCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); - // TODO: Known weird error formatting that is on our list to address expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', ]); expect(message.schema).toEqual({}); }); - test('it should NOT validate an array of strings', () => { + test('it should fail validation when supplied an array of strings', () => { const payload = ['some string']; const decoded = DefaultCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - 'Invalid value "some string" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts index 342cf8b0d7091..0d7e28e69cf71 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { CommentsArray, comments } from './comments'; +import { CommentsArray, comment } from './comment'; /** * Types the DefaultCommentsArray as: @@ -15,8 +15,8 @@ import { CommentsArray, comments } from './comments'; */ export const DefaultCommentsArray = new t.Type( 'DefaultCommentsArray', - t.array(comments).is, + t.array(comment).is, (input): Either => - input == null ? t.success([]) : t.array(comments).decode(input), + input == null ? t.success([]) : t.array(comment).decode(input), t.identity ); diff --git a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts index f5ef7d0ad96bd..eb960b5411904 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts @@ -10,11 +10,12 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; import { DefaultCreateCommentsArray } from './default_create_comments_array'; -import { CreateCommentsArray } from './create_comments'; -import { getCreateCommentsArrayMock } from './create_comments.mock'; +import { CreateCommentsArray } from './create_comment'; +import { getCreateCommentsArrayMock } from './create_comment.mock'; +import { getCommentsArrayMock } from './comment.mock'; describe('default_create_comments_array', () => { - test('it should validate an empty array', () => { + test('it should pass validation when an empty array', () => { const payload: CreateCommentsArray = []; const decoded = DefaultCreateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -23,7 +24,7 @@ describe('default_create_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should validate an array of comments', () => { + test('it should pass validation when an array of comments', () => { const payload: CreateCommentsArray = getCreateCommentsArrayMock(); const decoded = DefaultCreateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -32,25 +33,38 @@ describe('default_create_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should NOT validate an array of numbers', () => { + test('it should strip out "created_at" and "created_by" if they are passed in', () => { + const payload = getCommentsArrayMock(); + const decoded = DefaultCreateCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + // TODO: Known weird error formatting that is on our list to address + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual([ + { comment: 'some old comment' }, + { comment: 'some old comment' }, + ]); + }); + + test('it should not pass validation when an array of numbers', () => { const payload = [1]; const decoded = DefaultCreateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); // TODO: Known weird error formatting that is on our list to address expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<{| comment: string |}>"', + 'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"', ]); expect(message.schema).toEqual({}); }); - test('it should NOT validate an array of strings', () => { + test('it should not pass validation when an array of strings', () => { const payload = ['some string']; const decoded = DefaultCreateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "Array<{| comment: string |}>"', + 'Invalid value "some string" supplied to "Array<{| comment: NonEmptyString |}>"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts index 7fd79782836e3..4df888ba728fb 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { CreateCommentsArray, createComments } from './create_comments'; +import { CreateCommentsArray, createComment } from './create_comment'; /** * Types the DefaultCreateComments as: @@ -19,8 +19,8 @@ export const DefaultCreateCommentsArray = new t.Type< unknown >( 'DefaultCreateComments', - t.array(createComments).is, + t.array(createComment).is, (input): Either => - input == null ? t.success([]) : t.array(createComments).decode(input), + input == null ? t.success([]) : t.array(createComment).decode(input), t.identity ); diff --git a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts index b023e73cb9328..612148dc4ccab 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts @@ -10,11 +10,11 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; import { DefaultUpdateCommentsArray } from './default_update_comments_array'; -import { UpdateCommentsArray } from './update_comments'; -import { getUpdateCommentsArrayMock } from './update_comments.mock'; +import { UpdateCommentsArray } from './update_comment'; +import { getUpdateCommentsArrayMock } from './update_comment.mock'; describe('default_update_comments_array', () => { - test('it should validate an empty array', () => { + test('it should pass validation when supplied an empty array', () => { const payload: UpdateCommentsArray = []; const decoded = DefaultUpdateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -23,7 +23,7 @@ describe('default_update_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should validate an array of comments', () => { + test('it should pass validation when supplied an array of comments', () => { const payload: UpdateCommentsArray = getUpdateCommentsArrayMock(); const decoded = DefaultUpdateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -32,29 +32,26 @@ describe('default_update_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should NOT validate an array of numbers', () => { + test('it should fail validation when supplied an array of numbers', () => { const payload = [1]; const decoded = DefaultUpdateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); - // TODO: Known weird error formatting that is on our list to address expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', ]); expect(message.schema).toEqual({}); }); - test('it should NOT validate an array of strings', () => { + test('it should fail validation when supplied an array of strings', () => { const payload = ['some string']; const decoded = DefaultUpdateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "some string" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "some string" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', + 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts index 854b7cf7ada7e..35338dae64387 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { UpdateCommentsArray, updateCommentsArray } from './update_comments'; +import { UpdateCommentsArray, updateCommentsArray } from './update_comment'; /** * Types the DefaultCommentsUpdate as: diff --git a/x-pack/plugins/lists/common/schemas/types/index.ts b/x-pack/plugins/lists/common/schemas/types/index.ts index 463f7cfe51ce3..6b7e9fd17a1af 100644 --- a/x-pack/plugins/lists/common/schemas/types/index.ts +++ b/x-pack/plugins/lists/common/schemas/types/index.ts @@ -3,9 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -export * from './comments'; -export * from './create_comments'; -export * from './update_comments'; +export * from './comment'; +export * from './create_comment'; +export * from './update_comment'; export * from './default_comments_array'; export * from './default_create_comments_array'; export * from './default_update_comments_array'; diff --git a/x-pack/plugins/lists/common/schemas/types/update_comments.mock.ts b/x-pack/plugins/lists/common/schemas/types/update_comment.mock.ts similarity index 54% rename from x-pack/plugins/lists/common/schemas/types/update_comments.mock.ts rename to x-pack/plugins/lists/common/schemas/types/update_comment.mock.ts index 3e963c2607dc5..9b85a24abe40b 100644 --- a/x-pack/plugins/lists/common/schemas/types/update_comments.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/update_comment.mock.ts @@ -4,11 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getCommentsMock } from './comments.mock'; -import { getCreateCommentsMock } from './create_comments.mock'; -import { UpdateCommentsArray } from './update_comments'; +import { ID } from '../../constants.mock'; + +import { UpdateComment, UpdateCommentsArray } from './update_comment'; + +export const getUpdateCommentMock = (): UpdateComment => ({ + comment: 'some comment', + id: ID, +}); export const getUpdateCommentsArrayMock = (): UpdateCommentsArray => [ - getCommentsMock(), - getCreateCommentsMock(), + getUpdateCommentMock(), + getUpdateCommentMock(), ]; diff --git a/x-pack/plugins/lists/common/schemas/types/update_comment.test.ts b/x-pack/plugins/lists/common/schemas/types/update_comment.test.ts new file mode 100644 index 0000000000000..ac7716af40966 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/update_comment.test.ts @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { pipe } from 'fp-ts/lib/pipeable'; +import { left } from 'fp-ts/lib/Either'; + +import { foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getUpdateCommentMock, getUpdateCommentsArrayMock } from './update_comment.mock'; +import { + UpdateComment, + UpdateCommentsArray, + UpdateCommentsArrayOrUndefined, + updateComment, + updateCommentsArray, + updateCommentsArrayOrUndefined, +} from './update_comment'; + +describe('CommentsUpdate', () => { + describe('updateComment', () => { + test('it should pass validation when supplied typical comment update', () => { + const payload = getUpdateCommentMock(); + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should fail validation when supplied an undefined for "comment"', () => { + const payload = getUpdateCommentMock(); + delete payload.comment; + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "comment"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when supplied an empty string for "comment"', () => { + const payload = { ...getUpdateCommentMock(), comment: '' }; + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "comment"']); + expect(message.schema).toEqual({}); + }); + + test('it should pass validation when supplied an undefined for "id"', () => { + const payload = getUpdateCommentMock(); + delete payload.id; + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should fail validation when supplied an empty string for "id"', () => { + const payload = { ...getUpdateCommentMock(), id: '' }; + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "id"']); + expect(message.schema).toEqual({}); + }); + + test('it should strip out extra key passed in', () => { + const payload: UpdateComment & { + extraKey?: string; + } = { ...getUpdateCommentMock(), extraKey: 'some new value' }; + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(getUpdateCommentMock()); + }); + }); + + describe('updateCommentsArray', () => { + test('it should pass validation when supplied an array of comments', () => { + const payload = getUpdateCommentsArrayMock(); + const decoded = updateCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should fail validation when undefined', () => { + const payload = undefined; + const decoded = updateCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when array includes non comments types', () => { + const payload = ([1] as unknown) as UpdateCommentsArray; + const decoded = updateCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + ]); + expect(message.schema).toEqual({}); + }); + }); + + describe('updateCommentsArrayOrUndefined', () => { + test('it should pass validation when supplied an array of comments', () => { + const payload = getUpdateCommentsArrayMock(); + const decoded = updateCommentsArrayOrUndefined.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should pass validation when supplied when undefined', () => { + const payload = undefined; + const decoded = updateCommentsArrayOrUndefined.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should fail validation when array includes non comments types', () => { + const payload = ([1] as unknown) as UpdateCommentsArrayOrUndefined; + const decoded = updateCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + ]); + expect(message.schema).toEqual({}); + }); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/types/update_comments.ts b/x-pack/plugins/lists/common/schemas/types/update_comment.ts similarity index 58% rename from x-pack/plugins/lists/common/schemas/types/update_comments.ts rename to x-pack/plugins/lists/common/schemas/types/update_comment.ts index 4a21bfa363d45..b95812cb35bf9 100644 --- a/x-pack/plugins/lists/common/schemas/types/update_comments.ts +++ b/x-pack/plugins/lists/common/schemas/types/update_comment.ts @@ -5,10 +5,24 @@ */ import * as t from 'io-ts'; -import { comments } from './comments'; -import { createComments } from './create_comments'; +import { NonEmptyString } from '../../siem_common_deps'; +import { id } from '../common/schemas'; -export const updateCommentsArray = t.array(t.union([comments, createComments])); +export const updateComment = t.intersection([ + t.exact( + t.type({ + comment: NonEmptyString, + }) + ), + t.exact( + t.partial({ + id, + }) + ), +]); + +export type UpdateComment = t.TypeOf; +export const updateCommentsArray = t.array(updateComment); export type UpdateCommentsArray = t.TypeOf; export const updateCommentsArrayOrUndefined = t.union([updateCommentsArray, t.undefined]); export type UpdateCommentsArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/update_comments.test.ts b/x-pack/plugins/lists/common/schemas/types/update_comments.test.ts deleted file mode 100644 index 7668504b031b5..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/update_comments.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../siem_common_deps'; - -import { getUpdateCommentsArrayMock } from './update_comments.mock'; -import { - UpdateCommentsArray, - UpdateCommentsArrayOrUndefined, - updateCommentsArray, - updateCommentsArrayOrUndefined, -} from './update_comments'; -import { getCommentsMock } from './comments.mock'; -import { getCreateCommentsMock } from './create_comments.mock'; - -describe('CommentsUpdate', () => { - describe('updateCommentsArray', () => { - test('it should validate an array of comments', () => { - const payload = getUpdateCommentsArrayMock(); - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of existing comments', () => { - const payload = [getCommentsMock()]; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of new comments', () => { - const payload = [getCreateCommentsMock()]; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should not validate when undefined', () => { - const payload = undefined; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should not validate when array includes non comments types', () => { - const payload = ([1] as unknown) as UpdateCommentsArray; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('updateCommentsArrayOrUndefined', () => { - test('it should validate an array of comments', () => { - const payload = getUpdateCommentsArrayMock(); - const decoded = updateCommentsArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when undefined', () => { - const payload = undefined; - const decoded = updateCommentsArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should not validate when array includes non comments types', () => { - const payload = ([1] as unknown) as UpdateCommentsArrayOrUndefined; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - ]); - expect(message.schema).toEqual({}); - }); - }); -}); diff --git a/x-pack/plugins/lists/common/shared_exports.ts b/x-pack/plugins/lists/common/shared_exports.ts index dc0a9aa5926ef..1f6c65919b063 100644 --- a/x-pack/plugins/lists/common/shared_exports.ts +++ b/x-pack/plugins/lists/common/shared_exports.ts @@ -8,8 +8,8 @@ export { ListSchema, CommentsArray, CreateCommentsArray, - Comments, - CreateComments, + Comment, + CreateComment, ExceptionListSchema, ExceptionListItemSchema, CreateExceptionListSchema, @@ -28,6 +28,7 @@ export { OperatorType, OperatorTypeEnum, ExceptionListTypeEnum, + comment, exceptionListItemSchema, exceptionListType, createExceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts index 293435b3f6202..f5e0e7ae75700 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -14,6 +14,7 @@ import { exceptionListItemSchema, updateExceptionListItemSchema, } from '../../common/schemas'; +import { updateExceptionListItemValidate } from '../../common/schemas/request/update_exception_list_item_validation'; import { getExceptionListClient } from '.'; @@ -33,6 +34,11 @@ export const updateExceptionListItemRoute = (router: IRouter): void => { }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); + const validationErrors = updateExceptionListItemValidate(request.body); + if (validationErrors.length) { + return siemResponse.error({ body: validationErrors, statusCode: 400 }); + } + try { const { description, diff --git a/x-pack/plugins/lists/server/saved_objects/exception_list.ts b/x-pack/plugins/lists/server/saved_objects/exception_list.ts index 3bde3545837cf..f9e408833e069 100644 --- a/x-pack/plugins/lists/server/saved_objects/exception_list.ts +++ b/x-pack/plugins/lists/server/saved_objects/exception_list.ts @@ -83,6 +83,9 @@ export const exceptionListItemMapping: SavedObjectsType['mappings'] = { created_by: { type: 'keyword', }, + id: { + type: 'keyword', + }, updated_at: { type: 'keyword', }, diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json index da345fb930c04..81db909277595 100644 --- a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json +++ b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json @@ -1,17 +1,18 @@ { - "item_id": "simple_list_item", - "_tags": ["endpoint", "process", "malware", "os:windows"], - "tags": ["user added string for a tag", "malware"], - "type": "simple", - "description": "This is a sample change here this list", - "name": "Sample Endpoint Exception List update change", - "comments": [{ "comment": "this is a newly added comment" }], + "_tags": ["detection"], + "comments": [], + "description": "Test comments - exception list item", "entries": [ { - "field": "event.category", - "operator": "included", - "type": "match_any", - "value": ["process", "malware"] + "field": "host.name", + "type": "match", + "value": "rock01", + "operator": "included" } - ] + ], + "item_id": "993f43f7-325d-4df3-9338-964e77c37053", + "name": "Test comments - exception list item", + "namespace_type": "single", + "tags": [], + "type": "simple" } diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts index a90ec61aef4af..47c21735b45f4 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts @@ -64,7 +64,10 @@ export const createExceptionListItem = async ({ }: CreateExceptionListItemOptions): Promise => { const savedObjectType = getSavedObjectType({ namespaceType }); const dateNow = new Date().toISOString(); - const transformedComments = transformCreateCommentsToComments({ comments, user }); + const transformedComments = transformCreateCommentsToComments({ + incomingComments: comments, + user, + }); const savedObject = await savedObjectsClient.create(savedObjectType, { _tags, comments: transformedComments, diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts b/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts index 6f0c5195f2025..e3d96a9c3f6d0 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts @@ -5,15 +5,11 @@ */ import sinon from 'sinon'; import moment from 'moment'; +import uuid from 'uuid'; -import { USER } from '../../../common/constants.mock'; +import { transformCreateCommentsToComments, transformUpdateCommentsToComments } from './utils'; -import { - isCommentEqual, - transformCreateCommentsToComments, - transformUpdateComments, - transformUpdateCommentsToComments, -} from './utils'; +jest.mock('uuid/v4'); describe('utils', () => { const oldDate = '2020-03-17T20:34:51.337Z'; @@ -22,59 +18,43 @@ describe('utils', () => { let clock: sinon.SinonFakeTimers; beforeEach(() => { + ((uuid.v4 as unknown) as jest.Mock) + .mockImplementationOnce(() => '123') + .mockImplementationOnce(() => '456'); + clock = sinon.useFakeTimers(unix); }); afterEach(() => { clock.restore(); + jest.clearAllMocks(); + jest.restoreAllMocks(); + jest.resetAllMocks(); }); describe('#transformUpdateCommentsToComments', () => { - test('it returns empty array if "comments" is undefined and no comments exist', () => { + test('it formats new comments', () => { const comments = transformUpdateCommentsToComments({ - comments: undefined, + comments: [{ comment: 'Im a new comment' }], existingComments: [], user: 'lily', }); - expect(comments).toEqual([]); - }); - - test('it formats newly added comments', () => { - const comments = transformUpdateCommentsToComments({ - comments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'bane' }, - { comment: 'Im a new comment' }, - ], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'bane' }, - ], - user: 'lily', - }); - expect(comments).toEqual([ - { - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'bane', - }, { comment: 'Im a new comment', created_at: dateNow, created_by: 'lily', + id: '123', }, ]); }); - test('it formats multiple newly added comments', () => { + test('it formats new comments and preserves existing comments', () => { const comments = transformUpdateCommentsToComments({ - comments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - { comment: 'Im a new comment' }, - { comment: 'Im another new comment' }, - ], + comments: [{ comment: 'Im an old comment', id: '1' }, { comment: 'Im a new comment' }], existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, + { comment: 'Im an old comment', created_at: oldDate, created_by: 'bane', id: '1' }, ], user: 'lily', }); @@ -83,26 +63,23 @@ describe('utils', () => { { comment: 'Im an old comment', created_at: oldDate, - created_by: 'lily', + created_by: 'bane', + id: '1', }, { comment: 'Im a new comment', created_at: dateNow, created_by: 'lily', - }, - { - comment: 'Im another new comment', - created_at: dateNow, - created_by: 'lily', + id: '123', }, ]); }); - test('it should not throw if comments match existing comments', () => { + test('it returns existing comments if empty array passed for "comments"', () => { const comments = transformUpdateCommentsToComments({ - comments: [{ comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }], + comments: [], existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, + { comment: 'Im an old comment', created_at: oldDate, created_by: 'bane', id: '1' }, ], user: 'lily', }); @@ -111,170 +88,42 @@ describe('utils', () => { { comment: 'Im an old comment', created_at: oldDate, - created_by: 'lily', + created_by: 'bane', + id: '1', }, ]); }); - test('it does not throw if user tries to update one of their own existing comments', () => { + test('it acts as append only, only modifying new comments', () => { const comments = transformUpdateCommentsToComments({ - comments: [ - { - comment: 'Im an old comment that is trying to be updated', - created_at: oldDate, - created_by: 'lily', - }, - ], + comments: [{ comment: 'Im a new comment' }], existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, + { comment: 'Im an old comment', created_at: oldDate, created_by: 'bane', id: '1' }, ], user: 'lily', }); expect(comments).toEqual([ { - comment: 'Im an old comment that is trying to be updated', + comment: 'Im an old comment', created_at: oldDate, + created_by: 'bane', + id: '1', + }, + { + comment: 'Im a new comment', + created_at: dateNow, created_by: 'lily', - updated_at: dateNow, - updated_by: 'lily', + id: '123', }, ]); }); - - test('it throws an error if user tries to update their comment, without passing in the "created_at" and "created_by" properties', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [ - { - comment: 'Im an old comment that is trying to be updated', - }, - ], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot( - `"When trying to update a comment, \\"created_at\\" and \\"created_by\\" must be present"` - ); - }); - - test('it throws an error if user tries to delete comments', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot( - `"Comments cannot be deleted, only new comments may be added"` - ); - }); - - test('it throws if user tries to update existing comment timestamp', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [{ comment: 'Im an old comment', created_at: dateNow, created_by: 'lily' }], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'bane', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Not authorized to edit others comments"`); - }); - - test('it throws if user tries to update existing comment author', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [{ comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'me!' }, - ], - user: 'bane', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Not authorized to edit others comments"`); - }); - - test('it throws if user tries to update an existing comment that is not their own', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [ - { - comment: 'Im an old comment that is trying to be updated', - created_at: oldDate, - created_by: 'lily', - }, - ], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'bane', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Not authorized to edit others comments"`); - }); - - test('it throws if user tries to update order of comments', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [ - { comment: 'Im a new comment' }, - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot( - `"When trying to update a comment, \\"created_at\\" and \\"created_by\\" must be present"` - ); - }); - - test('it throws an error if user tries to add comment formatted as existing comment when none yet exist', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - { comment: 'Im a new comment' }, - ], - existingComments: [], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Only new comments may be added"`); - }); - - test('it throws if empty comment exists', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - { comment: ' ' }, - ], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Empty comments not allowed"`); - }); }); describe('#transformCreateCommentsToComments', () => { - test('it returns "undefined" if "comments" is "undefined"', () => { - const comments = transformCreateCommentsToComments({ - comments: undefined, - user: 'lily', - }); - - expect(comments).toBeUndefined(); - }); - test('it formats newly added comments', () => { const comments = transformCreateCommentsToComments({ - comments: [{ comment: 'Im a new comment' }, { comment: 'Im another new comment' }], + incomingComments: [{ comment: 'Im a new comment' }, { comment: 'Im another new comment' }], user: 'lily', }); @@ -283,178 +132,15 @@ describe('utils', () => { comment: 'Im a new comment', created_at: dateNow, created_by: 'lily', + id: '123', }, { comment: 'Im another new comment', created_at: dateNow, created_by: 'lily', + id: '456', }, ]); }); - - test('it throws an error if user tries to add an empty comment', () => { - expect(() => - transformCreateCommentsToComments({ - comments: [{ comment: ' ' }], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Empty comments not allowed"`); - }); - }); - - describe('#transformUpdateComments', () => { - test('it updates comment and adds "updated_at" and "updated_by" if content differs', () => { - const comments = transformUpdateComments({ - comment: { - comment: 'Im an old comment that is trying to be updated', - created_at: oldDate, - created_by: 'lily', - }, - existingComment: { - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'lily', - }, - user: 'lily', - }); - - expect(comments).toEqual({ - comment: 'Im an old comment that is trying to be updated', - created_at: oldDate, - created_by: 'lily', - updated_at: dateNow, - updated_by: 'lily', - }); - }); - - test('it does not update comment and add "updated_at" and "updated_by" if content is the same', () => { - const comments = transformUpdateComments({ - comment: { - comment: 'Im an old comment ', - created_at: oldDate, - created_by: 'lily', - }, - existingComment: { - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'lily', - }, - user: 'lily', - }); - - expect(comments).toEqual({ - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'lily', - }); - }); - - test('it throws if user tries to update an existing comment that is not their own', () => { - expect(() => - transformUpdateComments({ - comment: { - comment: 'Im an old comment that is trying to be updated', - created_at: oldDate, - created_by: 'lily', - }, - existingComment: { - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'lily', - }, - user: 'bane', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Not authorized to edit others comments"`); - }); - - test('it throws if user tries to update an existing comments timestamp', () => { - expect(() => - transformUpdateComments({ - comment: { - comment: 'Im an old comment', - created_at: dateNow, - created_by: 'lily', - }, - existingComment: { - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'lily', - }, - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Unable to update comment"`); - }); - }); - - describe('#isCommentEqual', () => { - test('it returns false if "comment" values differ', () => { - const result = isCommentEqual( - { - comment: 'some old comment', - created_at: oldDate, - created_by: USER, - }, - { - comment: 'some older comment', - created_at: oldDate, - created_by: USER, - } - ); - - expect(result).toBeFalsy(); - }); - - test('it returns false if "created_at" values differ', () => { - const result = isCommentEqual( - { - comment: 'some old comment', - created_at: oldDate, - created_by: USER, - }, - { - comment: 'some old comment', - created_at: dateNow, - created_by: USER, - } - ); - - expect(result).toBeFalsy(); - }); - - test('it returns false if "created_by" values differ', () => { - const result = isCommentEqual( - { - comment: 'some old comment', - created_at: oldDate, - created_by: USER, - }, - { - comment: 'some old comment', - created_at: oldDate, - created_by: 'lily', - } - ); - - expect(result).toBeFalsy(); - }); - - test('it returns true if comment values are equivalent', () => { - const result = isCommentEqual( - { - comment: 'some old comment', - created_at: oldDate, - created_by: USER, - }, - { - created_at: oldDate, - created_by: USER, - // Disabling to assure that order doesn't matter - // eslint-disable-next-line sort-keys - comment: 'some old comment', - } - ); - - expect(result).toBeTruthy(); - }); }); }); diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils.ts b/x-pack/plugins/lists/server/services/exception_lists/utils.ts index b168fae741822..836f642899086 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils.ts @@ -3,17 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import uuid from 'uuid'; import { SavedObject, SavedObjectsFindResponse, SavedObjectsUpdateResponse } from 'kibana/server'; import { NamespaceTypeArray } from '../../../common/schemas/types/default_namespace_array'; -import { ErrorWithStatusCode } from '../../error_with_status_code'; import { - Comments, CommentsArray, - CommentsArrayOrUndefined, - CreateComments, - CreateCommentsArrayOrUndefined, + CreateComment, + CreateCommentsArray, ExceptionListItemSchema, ExceptionListSchema, ExceptionListSoSchema, @@ -21,7 +18,6 @@ import { FoundExceptionListSchema, NamespaceType, UpdateCommentsArrayOrUndefined, - comments as commentsSchema, exceptionListItemType, exceptionListType, } from '../../../common/schemas'; @@ -296,17 +292,6 @@ export const transformSavedObjectsToFoundExceptionList = ({ }; }; -/* - * Determines whether two comments are equal, this is a very - * naive implementation, not meant to be used for deep equality of complex objects - */ -export const isCommentEqual = (commentA: Comments, commentB: Comments): boolean => { - const a = Object.values(commentA).sort().join(); - const b = Object.values(commentB).sort().join(); - - return a === b; -}; - export const transformUpdateCommentsToComments = ({ comments, existingComments, @@ -316,90 +301,28 @@ export const transformUpdateCommentsToComments = ({ existingComments: CommentsArray; user: string; }): CommentsArray => { - const newComments = comments ?? []; + const incomingComments = comments ?? []; + const newComments = incomingComments.filter((comment) => comment.id == null); + const newCommentsFormatted = transformCreateCommentsToComments({ + incomingComments: newComments, + user, + }); - if (newComments.length < existingComments.length) { - throw new ErrorWithStatusCode( - 'Comments cannot be deleted, only new comments may be added', - 403 - ); - } else { - return newComments.flatMap((c, index) => { - const existingComment = existingComments[index]; - - if (commentsSchema.is(existingComment) && !commentsSchema.is(c)) { - throw new ErrorWithStatusCode( - 'When trying to update a comment, "created_at" and "created_by" must be present', - 403 - ); - } else if (existingComment == null && commentsSchema.is(c)) { - throw new ErrorWithStatusCode('Only new comments may be added', 403); - } else if ( - commentsSchema.is(c) && - existingComment != null && - isCommentEqual(c, existingComment) - ) { - return existingComment; - } else if (commentsSchema.is(c) && existingComment != null) { - return transformUpdateComments({ comment: c, existingComment, user }); - } else { - return transformCreateCommentsToComments({ comments: [c], user }) ?? []; - } - }); - } -}; - -export const transformUpdateComments = ({ - comment, - existingComment, - user, -}: { - comment: Comments; - existingComment: Comments; - user: string; -}): Comments => { - if (comment.created_by !== user) { - // existing comment is being edited, can only be edited by author - throw new ErrorWithStatusCode('Not authorized to edit others comments', 401); - } else if (existingComment.created_at !== comment.created_at) { - throw new ErrorWithStatusCode('Unable to update comment', 403); - } else if (comment.comment.trim().length === 0) { - throw new ErrorWithStatusCode('Empty comments not allowed', 403); - } else if (comment.comment.trim() !== existingComment.comment) { - const dateNow = new Date().toISOString(); - - return { - ...existingComment, - comment: comment.comment, - updated_at: dateNow, - updated_by: user, - }; - } else { - return existingComment; - } + return [...existingComments, ...newCommentsFormatted]; }; export const transformCreateCommentsToComments = ({ - comments, + incomingComments, user, }: { - comments: CreateCommentsArrayOrUndefined; + incomingComments: CreateCommentsArray; user: string; -}): CommentsArrayOrUndefined => { +}): CommentsArray => { const dateNow = new Date().toISOString(); - if (comments != null) { - return comments.map((c: CreateComments) => { - if (c.comment.trim().length === 0) { - throw new ErrorWithStatusCode('Empty comments not allowed', 403); - } else { - return { - comment: c.comment, - created_at: dateNow, - created_by: user, - }; - } - }); - } else { - return comments; - } + return incomingComments.map((comment: CreateComment) => ({ + comment: comment.comment, + created_at: dateNow, + created_by: user, + id: uuid.v4(), + })); }; diff --git a/x-pack/plugins/security_solution/common/shared_imports.ts b/x-pack/plugins/security_solution/common/shared_imports.ts index 7fb94cea7b612..e28d1969b3976 100644 --- a/x-pack/plugins/security_solution/common/shared_imports.ts +++ b/x-pack/plugins/security_solution/common/shared_imports.ts @@ -8,8 +8,8 @@ export { ListSchema, CommentsArray, CreateCommentsArray, - Comments, - CreateComments, + Comment, + CreateComment, ExceptionListSchema, ExceptionListItemSchema, CreateExceptionListSchema, @@ -30,6 +30,7 @@ export { ExceptionListTypeEnum, exceptionListItemSchema, exceptionListType, + comment, createExceptionListItemSchema, listSchema, entry, diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx index db2d0540971de..22d14ec6bedb1 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx @@ -16,13 +16,13 @@ import { EuiCommentProps, EuiText, } from '@elastic/eui'; -import { Comments } from '../../../lists_plugin_deps'; +import { Comment } from '../../../shared_imports'; import * as i18n from './translations'; import { useCurrentUser } from '../../lib/kibana'; import { getFormattedComments } from './helpers'; interface AddExceptionCommentsProps { - exceptionItemComments?: Comments[]; + exceptionItemComments?: Comment[]; newCommentValue: string; newCommentOnChange: (value: string) => void; } diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index a4fe52eaacf4e..0f7e5b24ed8f9 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -38,7 +38,7 @@ import { useSignalIndex } from '../../../../detections/containers/detection_engi import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list'; import { AddExceptionComments } from '../add_exception_comments'; import { - enrichExceptionItemsWithComments, + enrichNewExceptionItemsWithComments, enrichExceptionItemsWithOS, defaultEndpointExceptionItems, entryHasListType, @@ -251,7 +251,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ let enriched: Array = []; enriched = comment !== '' - ? enrichExceptionItemsWithComments(exceptionItemsToAdd, [{ comment }]) + ? enrichNewExceptionItemsWithComments(exceptionItemsToAdd, [{ comment }]) : exceptionItemsToAdd; if (exceptionListType === 'endpoint') { const osTypes = retrieveAlertOsTypes(); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx index 1ec49425ce8fd..734434484fb4c 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx @@ -392,7 +392,7 @@ export const ExceptionBuilder = ({ )} { - addError(error, { title: i18n.EDIT_EXCEPTION_ERROR }); - onCancel(); + if (error.message.includes('Conflict')) { + setHasVersionConflict(true); + } else { + addError(error, { title: i18n.EDIT_EXCEPTION_ERROR }); + onCancel(); + } }, [addError, onCancel] ); @@ -147,8 +153,8 @@ export const EditExceptionModal = memo(function EditExceptionModal({ }, [shouldDisableBulkClose]); const isSubmitButtonDisabled = useMemo( - () => exceptionItemsToAdd.every((item) => item.entries.length === 0), - [exceptionItemsToAdd] + () => exceptionItemsToAdd.every((item) => item.entries.length === 0) || hasVersionConflict, + [exceptionItemsToAdd, hasVersionConflict] ); const handleBuilderOnChange = useCallback( @@ -177,11 +183,15 @@ export const EditExceptionModal = memo(function EditExceptionModal({ ); const enrichExceptionItems = useCallback(() => { - let enriched: Array = []; - enriched = enrichExceptionItemsWithComments(exceptionItemsToAdd, [ - ...(exceptionItem.comments ? exceptionItem.comments : []), - ...(comment !== '' ? [{ comment }] : []), - ]); + const [exceptionItemToEdit] = exceptionItemsToAdd; + let enriched: Array = [ + { + ...enrichExistingExceptionItemWithComments(exceptionItemToEdit, [ + ...exceptionItem.comments, + ...(comment.trim() !== '' ? [{ comment }] : []), + ]), + }, + ]; if (exceptionListType === 'endpoint') { const osTypes = exceptionItem._tags ? getOperatingSystems(exceptionItem._tags) : []; enriched = enrichExceptionItemsWithOS(enriched, osTypes); @@ -222,7 +232,7 @@ export const EditExceptionModal = memo(function EditExceptionModal({ listId={exceptionItem.list_id} listNamespaceType={exceptionItem.namespace_type} ruleName={ruleName} - isOrDisabled={false} + isOrDisabled isAndDisabled={false} isNestedDisabled={false} data-test-subj="edit-exception-modal-builder" @@ -263,6 +273,14 @@ export const EditExceptionModal = memo(function EditExceptionModal({ )} + {hasVersionConflict && ( + + +

      {i18n.VERSION_CONFLICT_ERROR_DESCRIPTION}

      +
      +
      + )} + {i18n.CANCEL} diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts index 6c5cb733b7a73..d09f0158b2e1d 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts @@ -67,3 +67,18 @@ export const EXCEPTION_BUILDER_INFO = i18n.translate( defaultMessage: "Alerts are generated when the rule's conditions are met, except when:", } ); + +export const VERSION_CONFLICT_ERROR_TITLE = i18n.translate( + 'xpack.securitySolution.exceptions.editException.versionConflictTitle', + { + defaultMessage: 'Sorry, there was an error', + } +); + +export const VERSION_CONFLICT_ERROR_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.exceptions.editException.versionConflictDescription', + { + defaultMessage: + "It appears this exception was updated since you first selected to edit it. Try clicking 'Cancel' and editing the exception again.", + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx index 78936d5d0da6f..5cb65ee6db8ff 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx @@ -18,7 +18,8 @@ import { formatOperatingSystems, getEntryValue, formatExceptionItemForUpdate, - enrichExceptionItemsWithComments, + enrichNewExceptionItemsWithComments, + enrichExistingExceptionItemWithComments, enrichExceptionItemsWithOS, entryHasListType, entryHasNonEcsType, @@ -35,14 +36,14 @@ import { existsOperator, doesNotExistOperator, } from '../autocomplete/operators'; -import { OperatorTypeEnum, OperatorEnum, EntryNested } from '../../../lists_plugin_deps'; +import { OperatorTypeEnum, OperatorEnum, EntryNested } from '../../../shared_imports'; import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getEntryMatchMock } from '../../../../../lists/common/schemas/types/entry_match.mock'; import { getEntryMatchAnyMock } from '../../../../../lists/common/schemas/types/entry_match_any.mock'; import { getEntryExistsMock } from '../../../../../lists/common/schemas/types/entry_exists.mock'; import { getEntryListMock } from '../../../../../lists/common/schemas/types/entry_list.mock'; -import { getCommentsArrayMock } from '../../../../../lists/common/schemas/types/comments.mock'; -import { ENTRIES } from '../../../../../lists/common/constants.mock'; +import { getCommentsArrayMock } from '../../../../../lists/common/schemas/types/comment.mock'; +import { ENTRIES, OLD_DATE_RELATIVE_TO_DATE_NOW } from '../../../../../lists/common/constants.mock'; import { CreateExceptionListItemSchema, ExceptionListItemSchema, @@ -410,12 +411,52 @@ describe('Exception helpers', () => { expect(result).toEqual(expected); }); }); + describe('#enrichExistingExceptionItemWithComments', () => { + test('it should return exception item with comments stripped of "created_by", "created_at", "updated_by", "updated_at" fields', () => { + const payload = getExceptionListItemSchemaMock(); + const comments = [ + { + comment: 'Im an existing comment', + created_at: OLD_DATE_RELATIVE_TO_DATE_NOW, + created_by: 'lily', + id: '1', + }, + { + comment: 'Im another existing comment', + created_at: OLD_DATE_RELATIVE_TO_DATE_NOW, + created_by: 'lily', + id: '2', + }, + { + comment: 'Im a new comment', + }, + ]; + const result = enrichExistingExceptionItemWithComments(payload, comments); + const expected = { + ...getExceptionListItemSchemaMock(), + comments: [ + { + comment: 'Im an existing comment', + id: '1', + }, + { + comment: 'Im another existing comment', + id: '2', + }, + { + comment: 'Im a new comment', + }, + ], + }; + expect(result).toEqual(expected); + }); + }); - describe('#enrichExceptionItemsWithComments', () => { + describe('#enrichNewExceptionItemsWithComments', () => { test('it should add comments to an exception item', () => { const payload = [getExceptionListItemSchemaMock()]; const comments = getCommentsArrayMock(); - const result = enrichExceptionItemsWithComments(payload, comments); + const result = enrichNewExceptionItemsWithComments(payload, comments); const expected = [ { ...getExceptionListItemSchemaMock(), @@ -428,7 +469,7 @@ describe('Exception helpers', () => { test('it should add comments to multiple exception items', () => { const payload = [getExceptionListItemSchemaMock(), getExceptionListItemSchemaMock()]; const comments = getCommentsArrayMock(); - const result = enrichExceptionItemsWithComments(payload, comments); + const result = enrichNewExceptionItemsWithComments(payload, comments); const expected = [ { ...getExceptionListItemSchemaMock(), diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index a54f20f56d56f..ee45f9b5de1fa 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -20,13 +20,14 @@ import { EXCEPTION_OPERATORS, isOperator } from '../autocomplete/operators'; import { OperatorOption } from '../autocomplete/types'; import { CommentsArray, - Comments, - CreateComments, + Comment, + CreateComment, Entry, ExceptionListItemSchema, NamespaceType, OperatorTypeEnum, CreateExceptionListItemSchema, + comment, entry, entriesNested, createExceptionListItemSchema, @@ -34,7 +35,7 @@ import { UpdateExceptionListItemSchema, ExceptionListType, EntryNested, -} from '../../../lists_plugin_deps'; +} from '../../../shared_imports'; import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; import { validate } from '../../../../common/validate'; import { TimelineNonEcsData } from '../../../graphql/types'; @@ -140,16 +141,16 @@ export const getTagsInclude = ({ * @param comments ExceptionItem.comments */ export const getFormattedComments = (comments: CommentsArray): EuiCommentProps[] => - comments.map((comment) => ({ - username: comment.created_by, - timestamp: moment(comment.created_at).format('on MMM Do YYYY @ HH:mm:ss'), + comments.map((commentItem) => ({ + username: commentItem.created_by, + timestamp: moment(commentItem.created_at).format('on MMM Do YYYY @ HH:mm:ss'), event: i18n.COMMENT_EVENT, - timelineIcon: , - children: {comment.comment}, + timelineIcon: , + children: {commentItem.comment}, actions: ( ), @@ -271,11 +272,11 @@ export const prepareExceptionItemsForBulkClose = ( /** * Adds new and existing comments to all new exceptionItems if not present already * @param exceptionItems new or existing ExceptionItem[] - * @param comments new Comments + * @param comments new Comment */ -export const enrichExceptionItemsWithComments = ( +export const enrichNewExceptionItemsWithComments = ( exceptionItems: Array, - comments: Array + comments: Array ): Array => { return exceptionItems.map((item: ExceptionListItemSchema | CreateExceptionListItemSchema) => { return { @@ -285,6 +286,36 @@ export const enrichExceptionItemsWithComments = ( }); }; +/** + * Adds new and existing comments to exceptionItem + * @param exceptionItem existing ExceptionItem + * @param comments array of comments that can include existing + * and new comments + */ +export const enrichExistingExceptionItemWithComments = ( + exceptionItem: ExceptionListItemSchema | CreateExceptionListItemSchema, + comments: Array +): ExceptionListItemSchema | CreateExceptionListItemSchema => { + const formattedComments = comments.map((item) => { + if (comment.is(item)) { + const { id, comment: existingComment } = item; + return { + id, + comment: existingComment, + }; + } else { + return { + comment: item.comment, + }; + } + }); + + return { + ...exceptionItem, + comments: formattedComments, + }; +}; + /** * Adds provided osTypes to all exceptionItems if not present already * @param exceptionItems new or existing ExceptionItem[] diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx index 8df7b51bb9d31..ab6588b67d5ba 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx @@ -12,7 +12,7 @@ import moment from 'moment-timezone'; import { ExceptionDetails } from './exception_details'; import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; -import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comments.mock'; +import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comment.mock'; describe('ExceptionDetails', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx index 56b029aaee81e..fec7354855935 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx @@ -11,7 +11,7 @@ import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { ExceptionItem } from './'; import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; -import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comments.mock'; +import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comment.mock'; addDecorator((storyFn) => ( ({ eui: euiLightVars, darkMode: false })}>{storyFn()} diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx index 90752f9450e4c..c9def092fda47 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx @@ -11,7 +11,7 @@ import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { ExceptionItem } from './'; import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; -import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comments.mock'; +import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comment.mock'; jest.mock('../../../../lib/kibana'); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx index 34dc47b9cd411..16eaef4136983 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx @@ -190,7 +190,8 @@ const ExceptionsViewerComponent = ({ const handleOnCancelExceptionModal = useCallback((): void => { setCurrentModal(null); - }, [setCurrentModal]); + handleFetchList(); + }, [setCurrentModal, handleFetchList]); const handleOnConfirmExceptionModal = useCallback((): void => { setCurrentModal(null); From ddff1c9ab9b0a36824ac0fdac97a957827cb8496 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 27 Jul 2020 17:50:46 -0600 Subject: [PATCH 10/75] [Security solution] Threat hunting test coverage improvements (#73276) --- .../components/markdown_editor/index.test.tsx | 49 ++++++ .../components/markdown_editor/index.tsx | 1 - .../navigation/breadcrumbs/index.test.ts | 74 +++++++++ .../utils/timeline/use_show_timeline.test.tsx | 33 ++++ .../components/manage_timeline/index.test.tsx | 145 ++++++++++++++++++ .../components/manage_timeline/index.tsx | 12 +- 6 files changed, 308 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.test.tsx new file mode 100644 index 0000000000000..b5e5b01189418 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.test.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; + +import { MarkdownEditor } from '.'; +import { TestProviders } from '../../mock'; + +describe('Markdown Editor', () => { + const onChange = jest.fn(); + const onCursorPositionUpdate = jest.fn(); + const defaultProps = { + content: 'hello world', + onChange, + onCursorPositionUpdate, + }; + beforeEach(() => { + jest.clearAllMocks(); + }); + test('it calls onChange with correct value', () => { + const wrapper = mount( + + + + ); + const newValue = 'a new string'; + wrapper + .find(`[data-test-subj="textAreaInput"]`) + .first() + .simulate('change', { target: { value: newValue } }); + expect(onChange).toBeCalledWith(newValue); + }); + test('it calls onCursorPositionUpdate with correct args', () => { + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="textAreaInput"]`).first().simulate('blur'); + expect(onCursorPositionUpdate).toBeCalledWith({ + start: 0, + end: 0, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.tsx index c40b3910ec152..d4ad4a11b60a3 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.tsx @@ -103,7 +103,6 @@ export const MarkdownEditor = React.memo<{ end: e!.target!.selectionEnd ?? 0, }); } - return false; }, [onCursorPositionUpdate] ); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index 7e508c28c62df..89aa77106933e 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -36,6 +36,13 @@ const getMockObject = ( ): RouteSpyState & TabNavigationProps => ({ detailName, navTabs: { + case: { + disabled: false, + href: '/app/security/cases', + id: 'case', + name: 'Cases', + urlKey: 'case', + }, hosts: { disabled: false, href: '/app/security/hosts', @@ -227,6 +234,73 @@ describe('Navigation Breadcrumbs', () => { { text: 'Flows', href: '' }, ]); }); + + test('should return Alerts breadcrumbs when supplied detection pathname', () => { + const breadcrumbs = getBreadcrumbsForRoute( + getMockObject('detections', '/', undefined), + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: '/app/security/overview' }, + { + text: 'Detections', + href: + "securitySolution:detections?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + ]); + }); + test('should return Cases breadcrumbs when supplied case pathname', () => { + const breadcrumbs = getBreadcrumbsForRoute( + getMockObject('case', '/', undefined), + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: '/app/security/overview' }, + { + text: 'Cases', + href: + "securitySolution:case?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + ]); + }); + test('should return Case details breadcrumbs when supplied case details pathname', () => { + const sampleCase = { + id: 'my-case-id', + name: 'Case name', + }; + const breadcrumbs = getBreadcrumbsForRoute( + { + ...getMockObject('case', `/${sampleCase.id}`, sampleCase.id), + state: { caseTitle: sampleCase.name }, + }, + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: '/app/security/overview' }, + { + text: 'Cases', + href: + "securitySolution:case?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + { + text: sampleCase.name, + href: `securitySolution:case/${sampleCase.id}?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + }, + ]); + }); + test('should return Admin breadcrumbs when supplied admin pathname', () => { + const breadcrumbs = getBreadcrumbsForRoute( + getMockObject('administration', '/', undefined), + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: '/app/security/overview' }, + { + text: 'Administration', + href: 'securitySolution:administration', + }, + ]); + }); }); describe('setBreadcrumbs()', () => { diff --git a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx new file mode 100644 index 0000000000000..db6e2536ce558 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { useShowTimeline } from './use_show_timeline'; +import { globalNode } from '../../mock'; + +describe('use show timeline', () => { + it('shows timeline for routes on default', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useShowTimeline()); + await waitForNextUpdate(); + const uninitializedTimeline = result.current; + expect(uninitializedTimeline).toEqual([true]); + }); + }); + it('hides timeline for blacklist routes', async () => { + await act(async () => { + Object.defineProperty(globalNode.window, 'location', { + value: { + pathname: `/cases/configure`, + }, + }); + const { result, waitForNextUpdate } = renderHook(() => useShowTimeline()); + await waitForNextUpdate(); + const uninitializedTimeline = result.current; + expect(uninitializedTimeline).toEqual([false]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx new file mode 100644 index 0000000000000..b918e5abc652b --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { getTimelineDefaults, useTimelineManager, UseTimelineManager } from './'; +import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { TimelineRowAction } from '../timeline/body/actions'; + +const isStringifiedComparisonEqual = (a: {}, b: {}): boolean => + JSON.stringify(a) === JSON.stringify(b); + +describe('useTimelineManager', () => { + const setupMock = coreMock.createSetup(); + const testId = 'coolness'; + const timelineDefaults = getTimelineDefaults(testId); + const timelineRowActions = () => []; + const mockFilterManager = new FilterManager(setupMock.uiSettings); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + it('initilizes an undefined timeline', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + const uninitializedTimeline = result.current.getManageTimelineById(testId); + expect(isStringifiedComparisonEqual(uninitializedTimeline, timelineDefaults)).toBeTruthy(); + }); + }); + it('getIndexToAddById', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + const data = result.current.getIndexToAddById(testId); + expect(data).toEqual(timelineDefaults.indexToAdd); + }); + }); + it('setIndexToAdd', async () => { + await act(async () => { + const indexToAddArgs = { id: testId, indexToAdd: ['example'] }; + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + result.current.initializeTimeline({ + id: testId, + timelineRowActions, + }); + result.current.setIndexToAdd(indexToAddArgs); + const data = result.current.getIndexToAddById(testId); + expect(data).toEqual(indexToAddArgs.indexToAdd); + }); + }); + it('setIsTimelineLoading', async () => { + await act(async () => { + const isLoadingArgs = { id: testId, isLoading: true }; + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + result.current.initializeTimeline({ + id: testId, + timelineRowActions, + }); + let timeline = result.current.getManageTimelineById(testId); + expect(timeline.isLoading).toBeFalsy(); + result.current.setIsTimelineLoading(isLoadingArgs); + timeline = result.current.getManageTimelineById(testId); + expect(timeline.isLoading).toBeTruthy(); + }); + }); + it('setTimelineRowActions', async () => { + await act(async () => { + const timelineRowActionsEx = () => [ + { id: 'wow', content: 'hey', displayType: 'icon', onClick: () => {} } as TimelineRowAction, + ]; + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + result.current.initializeTimeline({ + id: testId, + timelineRowActions, + }); + let timeline = result.current.getManageTimelineById(testId); + expect(timeline.timelineRowActions).toEqual(timelineRowActions); + result.current.setTimelineRowActions({ + id: testId, + timelineRowActions: timelineRowActionsEx, + }); + timeline = result.current.getManageTimelineById(testId); + expect(timeline.timelineRowActions).toEqual(timelineRowActionsEx); + }); + }); + it('getTimelineFilterManager undefined on uninitialized', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + const data = result.current.getTimelineFilterManager(testId); + expect(data).toEqual(undefined); + }); + }); + it('getTimelineFilterManager defined at initialize', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + result.current.initializeTimeline({ + id: testId, + timelineRowActions, + filterManager: mockFilterManager, + }); + const data = result.current.getTimelineFilterManager(testId); + expect(data).toEqual(mockFilterManager); + }); + }); + it('isManagedTimeline returns false when unset and then true when set', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + let data = result.current.isManagedTimeline(testId); + expect(data).toBeFalsy(); + result.current.initializeTimeline({ + id: testId, + timelineRowActions, + filterManager: mockFilterManager, + }); + data = result.current.isManagedTimeline(testId); + expect(data).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx index dba8506add0ad..a425f9b49add0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx @@ -137,7 +137,7 @@ const reducerManageTimeline = ( } }; -interface UseTimelineManager { +export interface UseTimelineManager { getIndexToAddById: (id: string) => string[] | null; getManageTimelineById: (id: string) => ManageTimeline; getTimelineFilterManager: (id: string) => FilterManager | undefined; @@ -152,7 +152,9 @@ interface UseTimelineManager { }) => void; } -const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseTimelineManager => { +export const useTimelineManager = ( + manageTimelineForTesting?: ManageTimelineById +): UseTimelineManager => { const [state, dispatch] = useReducer< (state: ManageTimelineById, action: ActionManageTimeline) => ManageTimelineById >(reducerManageTimeline, manageTimelineForTesting ?? initManageTimeline); @@ -241,12 +243,12 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT }; const init = { - getManageTimelineById: (id: string) => getTimelineDefaults(id), getIndexToAddById: (id: string) => null, + getManageTimelineById: (id: string) => getTimelineDefaults(id), getTimelineFilterManager: () => undefined, - setIndexToAdd: () => undefined, - isManagedTimeline: () => false, initializeTimeline: () => noop, + isManagedTimeline: () => false, + setIndexToAdd: () => undefined, setIsTimelineLoading: () => noop, setTimelineRowActions: () => noop, }; From ef83e772ca0357932c53dedfbb3ce68dc2361f55 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Mon, 27 Jul 2020 20:03:23 -0400 Subject: [PATCH 11/75] [Security Solution][Resolver] Show origin node details in panel on load (#73313) * show origin node details in panel on load * added comment Co-authored-by: Elastic Machine --- .../public/resolver/view/map.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/view/map.tsx b/x-pack/plugins/security_solution/public/resolver/view/map.tsx index 30aa4b63a138d..19c403f1257be 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/map.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/map.tsx @@ -8,7 +8,7 @@ /* eslint-disable react/display-name */ -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { useEffectOnce } from 'react-use'; import { EuiLoadingSpinner } from '@elastic/eui'; @@ -68,11 +68,25 @@ export const ResolverMap = React.memo(function ({ const hasError = useSelector(selectors.hasError); const activeDescendantId = useSelector(selectors.ariaActiveDescendant); const { colorMap } = useResolverTheme(); - const { cleanUpQueryParams } = useResolverQueryParams(); + const { + cleanUpQueryParams, + queryParams: { crumbId }, + pushToQueryParams, + } = useResolverQueryParams(); + useEffectOnce(() => { return () => cleanUpQueryParams(); }); + useEffect(() => { + // When you refresh the page after selecting a process in the table view (not the timeline view) + // The old crumbId still exists in the query string even though a resolver is no longer visible + // This just makes sure the activeDescendant and crumbId are in sync on load for that view as well as the timeline + if (activeDescendantId && crumbId !== activeDescendantId) { + pushToQueryParams({ crumbId: activeDescendantId, crumbEvent: '' }); + } + }, [crumbId, activeDescendantId, pushToQueryParams]); + return ( {isLoading ? ( From 8c52d39b9e757471f472a36eea30cdace30fd3ff Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Mon, 27 Jul 2020 20:34:08 -0400 Subject: [PATCH 12/75] [Security Solution] Show proper icon for termination status of all processes (#73235) * Show proper icon for termination status of all processes * Add basic test for isProcessTerminated selector --- .../resolver/store/data/selectors.test.ts | 29 +++++++++ .../public/resolver/store/data/selectors.ts | 13 ++++ .../resolver/store/mocks/endpoint_event.ts | 4 +- .../resolver/store/mocks/resolver_tree.ts | 63 +++++++++++++++++++ .../public/resolver/store/selectors.ts | 8 +++ .../public/resolver/view/panel.tsx | 20 +----- .../panels/panel_content_process_detail.tsx | 17 +++-- .../panels/panel_content_process_list.tsx | 14 ++--- .../view/panels/process_cube_icon.tsx | 4 +- 9 files changed, 131 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts index 9e1c396723a27..0826391a10688 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts @@ -13,6 +13,7 @@ import { mockTreeWithNoAncestorsAnd2Children, mockTreeWith2AncestorsAndNoChildren, mockTreeWith1AncestorAnd2ChildrenAndAllNodesHave2GraphableEvents, + mockTreeWithAllProcessesTerminated, } from '../mocks/resolver_tree'; import { uniquePidForProcess } from '../../models/process_event'; import { EndpointEvent } from '../../../../common/endpoint/types'; @@ -299,6 +300,34 @@ describe('data state', () => { expect(selectors.ariaFlowtoCandidate(state())(secondAncestorID)).toBe(null); }); }); + describe('with a tree with all processes terminated', () => { + const originID = 'c'; + const firstAncestorID = 'b'; + const secondAncestorID = 'a'; + beforeEach(() => { + actions.push({ + type: 'serverReturnedResolverData', + payload: { + result: mockTreeWithAllProcessesTerminated({ + originID, + firstAncestorID, + secondAncestorID, + }), + // this value doesn't matter + databaseDocumentID: '', + }, + }); + }); + it('should have origin as terminated', () => { + expect(selectors.isProcessTerminated(state())(originID)).toBe(true); + }); + it('should have first ancestor as termianted', () => { + expect(selectors.isProcessTerminated(state())(firstAncestorID)).toBe(true); + }); + it('should have second ancestor as terminated', () => { + expect(selectors.isProcessTerminated(state())(secondAncestorID)).toBe(true); + }); + }); describe('with a tree with 2 children and no ancestors', () => { const originID = 'c'; const firstChildID = 'd'; diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts index 1d65b406306a3..ea0cb8663d11d 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts @@ -105,6 +105,19 @@ export const terminatedProcesses = createSelector(resolverTreeResponse, function ); }); +/** + * A function that given an entity id returns a boolean indicating if the id is in the set of terminated processes. + */ +export const isProcessTerminated = createSelector(terminatedProcesses, function ( + /* eslint-disable no-shadow */ + terminatedProcesses + /* eslint-enable no-shadow */ +) { + return (entityId: string) => { + return terminatedProcesses.has(entityId); + }; +}); + /** * Process events that will be graphed. */ diff --git a/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts b/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts index b58ea73e1fdc7..8f2e0ad3a6d85 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts @@ -14,16 +14,18 @@ export function mockEndpointEvent({ name, parentEntityId, timestamp, + lifecycleType, }: { entityID: string; name: string; parentEntityId: string | undefined; timestamp: number; + lifecycleType?: string; }): EndpointEvent { return { '@timestamp': timestamp, event: { - type: 'start', + type: lifecycleType ? lifecycleType : 'start', category: 'process', }, process: { diff --git a/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts b/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts index 2860eec5a6ab6..ae43955f4c47c 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts @@ -46,6 +46,69 @@ export function mockTreeWith2AncestorsAndNoChildren({ } as unknown) as ResolverTree; } +export function mockTreeWithAllProcessesTerminated({ + originID, + firstAncestorID, + secondAncestorID, +}: { + secondAncestorID: string; + firstAncestorID: string; + originID: string; +}): ResolverTree { + const secondAncestor: ResolverEvent = mockEndpointEvent({ + entityID: secondAncestorID, + name: 'a', + parentEntityId: 'none', + timestamp: 0, + }); + const firstAncestor: ResolverEvent = mockEndpointEvent({ + entityID: firstAncestorID, + name: 'b', + parentEntityId: secondAncestorID, + timestamp: 1, + }); + const originEvent: ResolverEvent = mockEndpointEvent({ + entityID: originID, + name: 'c', + parentEntityId: firstAncestorID, + timestamp: 2, + }); + const secondAncestorTermination: ResolverEvent = mockEndpointEvent({ + entityID: secondAncestorID, + name: 'a', + parentEntityId: 'none', + timestamp: 0, + lifecycleType: 'end', + }); + const firstAncestorTermination: ResolverEvent = mockEndpointEvent({ + entityID: firstAncestorID, + name: 'b', + parentEntityId: secondAncestorID, + timestamp: 1, + lifecycleType: 'end', + }); + const originEventTermination: ResolverEvent = mockEndpointEvent({ + entityID: originID, + name: 'c', + parentEntityId: firstAncestorID, + timestamp: 2, + lifecycleType: 'end', + }); + return ({ + entityID: originID, + children: { + childNodes: [], + }, + ancestry: { + ancestors: [ + { lifecycle: [secondAncestor, secondAncestorTermination] }, + { lifecycle: [firstAncestor, firstAncestorTermination] }, + ], + }, + lifecycle: [originEvent, originEventTermination], + } as unknown) as ResolverTree; +} + export function mockTreeWithNoAncestorsAnd2Children({ originID, firstChildID, diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts index 66d7e04d118ed..87ef8d5d095ef 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts @@ -53,6 +53,14 @@ export const userIsPanning = composeSelectors(cameraStateSelector, cameraSelecto */ export const isAnimating = composeSelectors(cameraStateSelector, cameraSelectors.isAnimating); +/** + * Whether or not a given entity id is in the set of termination events. + */ +export const isProcessTerminated = composeSelectors( + dataStateSelector, + dataSelectors.isProcessTerminated +); + /** * Given a nodeID (aka entity_id) get the indexed process event. * Legacy functions take process events instead of nodeID, use this to get diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx index cb0acdc29ceb1..83d3930065da6 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx @@ -162,19 +162,10 @@ const PanelContent = memo(function PanelContent() { return 'processListWithCounts'; }, [uiSelectedEvent, crumbEvent, crumbId, graphableProcessEntityIds]); - const terminatedProcesses = useSelector(selectors.terminatedProcesses); - const processEntityId = uiSelectedEvent ? event.entityId(uiSelectedEvent) : undefined; - const isProcessTerminated = processEntityId ? terminatedProcesses.has(processEntityId) : false; - const panelInstance = useMemo(() => { if (panelToShow === 'processDetails') { return ( - + ); } @@ -213,13 +204,7 @@ const PanelContent = memo(function PanelContent() { ); } // The default 'Event List' / 'List of all processes' view - return ( - - ); + return ; }, [ uiSelectedEvent, crumbEvent, @@ -227,7 +212,6 @@ const PanelContent = memo(function PanelContent() { pushToQueryParams, relatedStatsForIdFromParams, panelToShow, - isProcessTerminated, ]); return <>{panelInstance}; diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx index 5d90cd11d31af..29c7676d2167d 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { memo, useMemo } from 'react'; +import { useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { htmlIdGenerator, @@ -15,6 +16,7 @@ import { } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from 'react-intl'; +import * as selectors from '../../store/selectors'; import * as event from '../../../../common/endpoint/models/event'; import { CrumbInfo, formatDate, StyledBreadcrumbs } from './panel_content_utilities'; import { @@ -41,16 +43,14 @@ const StyledDescriptionList = styled(EuiDescriptionList)` */ export const ProcessDetails = memo(function ProcessDetails({ processEvent, - isProcessTerminated, - isProcessOrigin, pushToQueryParams, }: { processEvent: ResolverEvent; - isProcessTerminated: boolean; - isProcessOrigin: boolean; pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown; }) { const processName = event.eventName(processEvent); + const entityId = event.entityId(processEvent); + const isProcessTerminated = useSelector(selectors.isProcessTerminated)(entityId); const processInfoEntry = useMemo(() => { const eventTime = event.eventTimestamp(processEvent); const dateTime = eventTime ? formatDate(eventTime) : ''; @@ -151,8 +151,8 @@ export const ProcessDetails = memo(function ProcessDetails({ if (!processEvent) { return { descriptionText: '' }; } - return cubeAssetsForNode(isProcessTerminated, isProcessOrigin); - }, [processEvent, cubeAssetsForNode, isProcessTerminated, isProcessOrigin]); + return cubeAssetsForNode(isProcessTerminated, false); + }, [processEvent, cubeAssetsForNode, isProcessTerminated]); const titleId = useMemo(() => htmlIdGenerator('resolverTable')(), []); return ( @@ -161,10 +161,7 @@ export const ProcessDetails = memo(function ProcessDetails({

      - + {processName}

      diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx index 6f9bfad8c08c2..efb96cde431e5 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx @@ -50,12 +50,8 @@ const StyledLimitWarning = styled(LimitWarning)` */ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ pushToQueryParams, - isProcessTerminated, - isProcessOrigin, }: { pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown; - isProcessTerminated: boolean; - isProcessOrigin: boolean; }) { interface ProcessTableView { name: string; @@ -65,6 +61,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ const dispatch = useResolverDispatch(); const { timestamp } = useContext(SideEffectContext); + const isProcessTerminated = useSelector(selectors.isProcessTerminated); const handleBringIntoViewClick = useCallback( (processTableViewItem) => { dispatch({ @@ -92,6 +89,8 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ sortable: true, truncateText: true, render(name: string, item: ProcessTableView) { + const entityId = event.entityId(item.event); + const isTerminated = isProcessTerminated(entityId); return name === '' ? ( {i18n.translate( @@ -108,10 +107,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ pushToQueryParams({ crumbId: event.entityId(item.event), crumbEvent: '' }); }} > - + {name} ); @@ -143,7 +139,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ }, }, ], - [pushToQueryParams, handleBringIntoViewClick, isProcessOrigin, isProcessTerminated] + [pushToQueryParams, handleBringIntoViewClick, isProcessTerminated] ); const { processNodePositions } = useSelector(selectors.layout); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx index 98eea51a011b6..b073324b27f9b 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx @@ -13,13 +13,11 @@ import { useResolverTheme } from '../assets'; */ export const CubeForProcess = memo(function CubeForProcess({ isProcessTerminated, - isProcessOrigin, }: { isProcessTerminated: boolean; - isProcessOrigin: boolean; }) { const { cubeAssetsForNode } = useResolverTheme(); - const { cubeSymbol, descriptionText } = cubeAssetsForNode(isProcessTerminated, isProcessOrigin); + const { cubeSymbol, descriptionText } = cubeAssetsForNode(isProcessTerminated, false); return ( <> From 765c2d1ad3308a3c3af50f8d67b80579aeb13a9a Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Mon, 27 Jul 2020 19:52:28 -0600 Subject: [PATCH 13/75] [Security Solution][ML] Updates siem group name to security (#73218) ## Summary Resolves https://github.com/elastic/kibana/issues/69319 Updates `siem` grouping to `security`, and enables cloudtrail module, fixing mis-match between the newly updated modules (https://github.com/elastic/kibana/pull/71696).

      Also updates all module icons to be consistent: Auditbeat (Before/After):

      Packetbeat (Before/After):

      Winlogbeat (Before/After):

      - [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/master/packages/kbn-i18n/README.md) - [X] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - Working w/ @benskelker on updated ML Jobs & nomenclature --- .../models/data_recognizer/modules/siem_auditbeat/logo.json | 2 +- .../data_recognizer/modules/siem_auditbeat_auth/logo.json | 4 ++-- .../data_recognizer/modules/siem_packetbeat/logo.json | 4 ++-- .../data_recognizer/modules/siem_winlogbeat/logo.json | 2 +- .../data_recognizer/modules/siem_winlogbeat_auth/logo.json | 4 ++-- .../public/common/components/ml_popover/api.tsx | 2 +- .../common/components/ml_popover/hooks/translations.ts | 2 +- .../components/ml_popover/hooks/use_siem_jobs_helpers.tsx | 2 +- .../ml_popover/jobs_table/filters/groups_filter_popover.tsx | 6 +++--- .../public/common/components/ml_popover/ml_modules.tsx | 1 + .../detections/components/rules/ml_job_select/index.tsx | 2 +- .../server/usage/detections/detections_helpers.ts | 4 +++- 12 files changed, 19 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json index 40a5c59677147..dfd22f6b1140b 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json @@ -1,3 +1,3 @@ { - "icon": "securityAnalyticsApp" + "icon": "logoSecurity" } diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json index 6b02648ccf287..dfd22f6b1140b 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json @@ -1,3 +1,3 @@ { - "icon": "securityAnalyticsApp" -} \ No newline at end of file + "icon": "logoSecurity" +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json index 6b02648ccf287..dfd22f6b1140b 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json @@ -1,3 +1,3 @@ { - "icon": "securityAnalyticsApp" -} \ No newline at end of file + "icon": "logoSecurity" +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json index 40a5c59677147..dfd22f6b1140b 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json @@ -1,3 +1,3 @@ { - "icon": "securityAnalyticsApp" + "icon": "logoSecurity" } diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json index 6b02648ccf287..dfd22f6b1140b 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json @@ -1,3 +1,3 @@ { - "icon": "securityAnalyticsApp" -} \ No newline at end of file + "icon": "logoSecurity" +} diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.tsx index b4da4fa79e035..7c72098209a06 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.tsx @@ -71,7 +71,7 @@ export const setupMlJob = async ({ configTemplate, indexPatternName = 'auditbeat-*', jobIdErrorFilter = [], - groups = ['siem'], + groups = ['security'], prefix = '', }: MlSetupArgs): Promise => { const response = await KibanaServices.get().http.fetch( diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/translations.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/translations.ts index 2b37c437866e0..7b29bab2e38f3 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/translations.ts @@ -9,6 +9,6 @@ import { i18n } from '@kbn/i18n'; export const SIEM_JOB_FETCH_FAILURE = i18n.translate( 'xpack.securitySolution.components.mlPopup.hooks.errors.siemJobFetchFailureTitle', { - defaultMessage: 'SIEM job fetch failure', + defaultMessage: 'Security job fetch failure', } ); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_siem_jobs_helpers.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_siem_jobs_helpers.tsx index 658d2659282ce..adbd712ffeb3e 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_siem_jobs_helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_siem_jobs_helpers.tsx @@ -104,7 +104,7 @@ export const getInstalledJobs = ( compatibleModuleIds: string[] ): SiemJob[] => jobSummaryData - .filter(({ groups }) => groups.includes('siem')) + .filter(({ groups }) => groups.includes('siem') || groups.includes('security')) .map((jobSummary) => ({ ...jobSummary, ...getAugmentedFields(jobSummary.id, moduleJobs, compatibleModuleIds), diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx index 1aa3ad630306e..d879942b8b101 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx @@ -25,8 +25,8 @@ interface GroupsFilterPopoverProps { /** * Popover for selecting which SiemJob groups to filter on. Component extracts unique groups and - * their counts from the provided SiemJobs. The 'siem' group is filtered out as all jobs will be - * siem jobs + * their counts from the provided SiemJobs. The 'siem' & 'security' groups are filtered out as all jobs will be + * siem/security jobs * * @param siemJobs jobs to fetch groups from to display for filtering * @param onSelectedGroupsChanged change listener to be notified when group selection changes @@ -41,7 +41,7 @@ export const GroupsFilterPopoverComponent = ({ const groups = siemJobs .map((j) => j.groups) .flat() - .filter((g) => g !== 'siem'); + .filter((g) => g !== 'siem' && g !== 'security'); const uniqueGroups = Array.from(new Set(groups)); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx index b956cf2c1494c..4dccba08590a4 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx @@ -12,6 +12,7 @@ export const mlModules: string[] = [ 'siem_auditbeat', 'siem_auditbeat_auth', + 'siem_cloudtrail', 'siem_packetbeat', 'siem_winlogbeat', 'siem_winlogbeat_auth', diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx index cb084d4daa782..cdfdf4ca6b66b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx @@ -41,7 +41,7 @@ const HelpText: React.FC<{ href: string; showEnableWarning: boolean }> = ({ <> diff --git a/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts b/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts index e9d4f3aa426f4..f9905c373291c 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts @@ -176,7 +176,9 @@ export const getMlJobsUsage = async (ml: MlPluginSetup | undefined): Promise module.jobs); - const jobs = await ml.jobServiceProvider(internalMlClient, fakeRequest).jobsSummary(['siem']); + const jobs = await ml + .jobServiceProvider(internalMlClient, fakeRequest) + .jobsSummary(['siem', 'security']); jobsUsage = jobs.reduce((usage, job) => { const isElastic = moduleJobs.some((moduleJob) => moduleJob.id === job.id); From 5af2c1080a85b247324d7b1fd36428c6d561ac55 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Mon, 27 Jul 2020 19:21:14 -0700 Subject: [PATCH 14/75] Exclude `version` from package config attributes that are copied, add safeguard to package config bulk create (#73128) Co-authored-by: Elastic Machine --- .../ingest_manager/server/services/agent_config.ts | 12 +++++------- .../ingest_manager/server/services/package_config.ts | 5 ++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/services/agent_config.ts b/x-pack/plugins/ingest_manager/server/services/agent_config.ts index 0a9adc1f1c593..3886146e28806 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_config.ts @@ -233,16 +233,14 @@ class AgentConfigService { if (baseAgentConfig.package_configs.length) { const newPackageConfigs = (baseAgentConfig.package_configs as PackageConfig[]).map( (packageConfig: PackageConfig) => { - const { id: packageConfigId, ...newPackageConfig } = packageConfig; + const { id: packageConfigId, version, ...newPackageConfig } = packageConfig; return newPackageConfig; } ); - await packageConfigService.bulkCreate( - soClient, - newPackageConfigs, - newAgentConfig.id, - options - ); + await packageConfigService.bulkCreate(soClient, newPackageConfigs, newAgentConfig.id, { + ...options, + bumpConfigRevision: false, + }); } // Get updated config diff --git a/x-pack/plugins/ingest_manager/server/services/package_config.ts b/x-pack/plugins/ingest_manager/server/services/package_config.ts index c2d465cf7c73f..5d1c5d1717714 100644 --- a/x-pack/plugins/ingest_manager/server/services/package_config.ts +++ b/x-pack/plugins/ingest_manager/server/services/package_config.ts @@ -121,7 +121,7 @@ class PackageConfigService { options?: { user?: AuthenticatedUser; bumpConfigRevision?: boolean } ): Promise { const isoDate = new Date().toISOString(); - const { saved_objects: newSos } = await soClient.bulkCreate( + const { saved_objects } = await soClient.bulkCreate( packageConfigs.map((packageConfig) => ({ type: SAVED_OBJECT_TYPE, attributes: { @@ -136,6 +136,9 @@ class PackageConfigService { })) ); + // Filter out invalid SOs + const newSos = saved_objects.filter((so) => !so.error && so.attributes); + // Assign it to the given agent config await agentConfigService.assignPackageConfigs( soClient, From 82d7e7db699bbe961da5eb8b2218de5d2c2e7e18 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Mon, 27 Jul 2020 19:21:41 -0700 Subject: [PATCH 15/75] [Ingest Manager] Convert select agent config step to use combo box (#73172) * Initial pass at using combo box instead of selectable for agent configs * Hide agent count messaging if fleet isn't set up * Fix types * Fix i18n * Fix i18n again * Add comment explaining styling Co-authored-by: Elastic Machine --- .../step_select_config.tsx | 227 +++++++++++------- .../list_page/components/create_config.tsx | 2 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 4 files changed, 145 insertions(+), 86 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/step_select_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/step_select_config.tsx index 91c80b7eee4c8..6f06530100d71 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/step_select_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/step_select_config.tsx @@ -3,17 +3,19 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState, Fragment } from 'react'; +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, - EuiSelectable, - EuiSpacer, + EuiComboBox, + EuiComboBoxOptionOption, EuiTextColor, EuiPortal, - EuiButtonEmpty, + EuiFormRow, + EuiLink, } from '@elastic/eui'; import { Error } from '../../../components'; import { AgentConfig, PackageInfo, GetAgentConfigsResponseItem } from '../../../types'; @@ -23,9 +25,30 @@ import { useGetAgentConfigs, sendGetOneAgentConfig, useCapabilities, + useFleetStatus, } from '../../../hooks'; import { CreateAgentConfigFlyout } from '../list_page/components'; +const AgentConfigWrapper = styled(EuiFormRow)` + .euiFormRow__label { + width: 100%; + } +`; + +// Custom styling for drop down list items due to: +// 1) the max-width and overflow properties is added to prevent long config +// names/descriptions from overflowing the flex items +// 2) max-width is built from the grow property on the flex items because the value +// changes based on if Fleet is enabled/setup or not +const AgentConfigNameColumn = styled(EuiFlexItem)` + max-width: ${(props) => `${((props.grow as number) / 9) * 100}%`}; + overflow: hidden; +`; +const AgentConfigDescriptionColumn = styled(EuiFlexItem)` + max-width: ${(props) => `${((props.grow as number) / 9) * 100}%`}; + overflow: hidden; +`; + export const StepSelectConfig: React.FunctionComponent<{ pkgkey: string; updatePackageInfo: (packageInfo: PackageInfo | undefined) => void; @@ -33,6 +56,8 @@ export const StepSelectConfig: React.FunctionComponent<{ updateAgentConfig: (config: AgentConfig | undefined) => void; setIsLoadingSecondStep: (isLoading: boolean) => void; }> = ({ pkgkey, updatePackageInfo, agentConfig, updateAgentConfig, setIsLoadingSecondStep }) => { + const { isReady: isFleetReady } = useFleetStatus(); + // Selected config state const [selectedConfigId, setSelectedConfigId] = useState( agentConfig ? agentConfig.id : undefined @@ -106,6 +131,40 @@ export const StepSelectConfig: React.FunctionComponent<{ } }, [selectedConfigId, agentConfig, updateAgentConfig, setIsLoadingSecondStep]); + const agentConfigOptions: Array> = packageInfoData + ? agentConfigs.map((agentConf) => { + const alreadyHasLimitedPackage = + (isLimitedPackage && + doesAgentConfigAlreadyIncludePackage(agentConf, packageInfoData.response.name)) || + false; + return { + label: agentConf.name, + value: agentConf.id, + disabled: alreadyHasLimitedPackage, + 'data-test-subj': 'agentConfigItem', + }; + }) + : []; + + const selectedConfigOption = agentConfigOptions.find( + (option) => option.value === selectedConfigId + ); + + // Try to select default agent config + useEffect(() => { + if (!selectedConfigId && agentConfigs.length && agentConfigOptions.length) { + const defaultAgentConfig = agentConfigs.find((config) => config.is_default); + if (defaultAgentConfig) { + const defaultAgentConfigOption = agentConfigOptions.find( + (option) => option.value === defaultAgentConfig.id + ); + if (defaultAgentConfigOption && !defaultAgentConfigOption.disabled) { + setSelectedConfigId(defaultAgentConfig.id); + } + } + } + }, [agentConfigs, agentConfigOptions, selectedConfigId]); + // Display package error if there is one if (packageInfoError) { return ( @@ -154,77 +213,95 @@ export const StepSelectConfig: React.FunctionComponent<{ ) : null} - { - const alreadyHasLimitedPackage = - (isLimitedPackage && - packageInfoData && - doesAgentConfigAlreadyIncludePackage(agentConf, packageInfoData.response.name)) || - false; - return { - label: agentConf.name, - key: agentConf.id, - checked: selectedConfigId === agentConf.id ? 'on' : undefined, - disabled: alreadyHasLimitedPackage, - 'data-test-subj': 'agentConfigItem', - }; - })} - renderOption={(option) => ( - - {option.label} + - - {agentConfigsById[option.key!].description} - + - - - +
      + setIsCreateAgentConfigFlyoutOpen(true)} + > + + +
      - )} - listProps={{ - bordered: true, - }} - searchProps={{ - placeholder: i18n.translate( - 'xpack.ingestManager.createPackageConfig.StepSelectConfig.filterAgentConfigsInputPlaceholder', + } + helpText={ + isFleetReady && selectedConfigId ? ( + + ) : null + } + > + { - const selectedOption = options.find((option) => option.checked === 'on'); - if (selectedOption) { - if (selectedOption.key !== selectedConfigId) { - setSelectedConfigId(selectedOption.key); + )} + singleSelection={{ asPlainText: true }} + isClearable={false} + fullWidth={true} + isLoading={isAgentConfigsLoading || isPackageInfoLoading} + options={agentConfigOptions} + renderOption={(option: EuiComboBoxOptionOption) => { + return ( + + + {option.label} + + + + {agentConfigsById[option.value!].description} + + + {isFleetReady ? ( + + + + + + ) : null} + + ); + }} + selectedOptions={selectedConfigOption ? [selectedConfigOption] : []} + onChange={(options) => { + const selectedOption = options[0] || undefined; + if (selectedOption) { + if (selectedOption.value !== selectedConfigId) { + setSelectedConfigId(selectedOption.value); + } + } else { + setSelectedConfigId(undefined); } - } else { - setSelectedConfigId(undefined); - } - }} - > - {(list, search) => ( - - {search} - - {list} - - )} -
      + }} + /> +
      {/* Display selected agent config error if there is one */} {selectedConfigError ? ( @@ -240,22 +317,6 @@ export const StepSelectConfig: React.FunctionComponent<{ />
      ) : null} - -
      - setIsCreateAgentConfigFlyoutOpen(true)} - flush="left" - size="s" - > - - -
      -
      ); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx index fc593705a4e1b..749716b473c85 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx @@ -160,7 +160,7 @@ export const CreateAgentConfigFlyout: React.FunctionComponent = ({ ); return ( - + onClose()} size="l" maxWidth={400} {...restOfProps}> {header} {body} {footer} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index cf79f463b35cb..ee7d1e0298d00 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8108,7 +8108,6 @@ "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingAgentConfigsTitle": "エージェント構成の読み込みエラー", "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingPackageTitle": "パッケージ情報の読み込みエラー", "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingSelectedAgentConfigTitle": "選択したエージェント構成の読み込みエラー", - "xpack.ingestManager.createPackageConfig.StepSelectConfig.filterAgentConfigsInputPlaceholder": "エージェント構成の検索", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingConfigTitle": "エージェント構成情報の読み込みエラー", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingPackagesTitle": "統合の読み込みエラー", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingSelectedPackageTitle": "選択した統合の読み込みエラー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b45fe1baa9e9a..30c932c362a4f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8113,7 +8113,6 @@ "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingAgentConfigsTitle": "加载代理配置时出错", "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingPackageTitle": "加载软件包信息时出错", "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingSelectedAgentConfigTitle": "加载选定代理配置时出错", - "xpack.ingestManager.createPackageConfig.StepSelectConfig.filterAgentConfigsInputPlaceholder": "搜索代理配置", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingConfigTitle": "加载代理配置信息时出错", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingPackagesTitle": "加载集成时出错", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingSelectedPackageTitle": "加载选定集成时出错", From cc84ee31856c8eb70d5a2d1093b21678d5842f88 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Mon, 27 Jul 2020 21:28:39 -0500 Subject: [PATCH 16/75] [Metrics UI] Saved views bugs (#72518) * Add test for logs and metrics telemetry * wait before you go * Remove kubenetes * Fix type check * Add back kubernetes test * Remove kubernetes * Don't allow deleting default default view. * Fix bug with duplicate loads of data. Because the load data function takes options.source and the source of options can change, we need to remove it from deps * Remove unused variable * Reload when loadData function is changed * Don't send the request immediately Co-authored-by: Elastic Machine --- .../public/components/saved_views/manage_views_flyout.tsx | 4 ++++ .../public/components/saved_views/toolbar_control.tsx | 2 +- .../infra/public/containers/saved_view/saved_view.tsx | 8 +++----- .../pages/metrics/inventory_view/components/layout.tsx | 3 ++- .../infra/public/pages/metrics/metrics_explorer/index.tsx | 3 ++- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx index fa9b45558e491..698034f8154d1 100644 --- a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx @@ -96,6 +96,10 @@ export function SavedViewManageViewsFlyout({ const renderDeleteAction = useCallback( (item: SavedView) => { + if (item.id === '0') { + return <>; + } + return ( (props: Props) { /> - + { const { data, loading, find, error: errorOnFind, hasView } = useFindSavedObject< SavedViewSavedObject >(viewType); - + const [shouldLoadDefault] = useState(props.shouldLoadDefault); const [currentView, setCurrentView] = useState | null>(null); const [loadingDefaultView, setLoadingDefaultView] = useState(null); const { create, error: errorOnCreate, data: createdViewData, createdId } = useCreateSavedObject( @@ -211,8 +211,6 @@ export const useSavedView = (props: Props) => { }, [setCurrentView, defaultViewId, defaultViewState]); useEffect(() => { - const shouldLoadDefault = props.shouldLoadDefault; - if (loadingDefaultView || currentView || !shouldLoadDefault) { return; } @@ -225,7 +223,7 @@ export const useSavedView = (props: Props) => { } }, [ loadDefaultView, - props.shouldLoadDefault, + shouldLoadDefault, setDefault, loadingDefaultView, currentView, @@ -246,7 +244,7 @@ export const useSavedView = (props: Props) => { errorOnUpdate, errorOnFind, errorOnCreate: createError, - shouldLoadDefault: props.shouldLoadDefault, + shouldLoadDefault, makeDefault, sourceIsLoading, deleteView, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index fddd92128708a..ad92c054ee459 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -55,7 +55,8 @@ export const Layout = () => { sourceId, currentTime, accountId, - region + region, + false ); const options = { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx index cd875ae54071c..20efca79650a1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx @@ -57,7 +57,8 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl // load metrics explorer data after default view loaded, unless we're not loading a view loadData(); } - }, [loadData, currentView, shouldLoadDefault]); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [loadData, shouldLoadDefault]); return ( From 281c76767b21c458a237474d77211f18883d8d68 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Tue, 28 Jul 2020 09:23:28 +0200 Subject: [PATCH 17/75] updates cypress to v4.11.0 (#73327) Co-authored-by: Elastic Machine --- x-pack/package.json | 2 +- yarn.lock | 170 +++++++++++++++----------------------------- 2 files changed, 60 insertions(+), 112 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index dee99d6f0ddac..76655f75cadcc 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -131,7 +131,7 @@ "cheerio": "0.22.0", "commander": "3.0.2", "copy-webpack-plugin": "^6.0.2", - "cypress": "4.5.0", + "cypress": "4.11.0", "cypress-multi-reporters": "^1.2.3", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", diff --git a/yarn.lock b/yarn.lock index 899bc45fbe3fb..c1328731db150 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4717,21 +4717,11 @@ resolved "https://registry.yarnpkg.com/@types/base64-js/-/base64-js-1.2.5.tgz#582b2476169a6cba460a214d476c744441d873d5" integrity sha1-WCskdhaabLpGCiFNR2x0REHYc9U= -"@types/blob-util@1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a" - integrity sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w== - "@types/bluebird@*", "@types/bluebird@^3.1.1": version "3.5.30" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.30.tgz#ee034a0eeea8b84ed868b1aa60d690b08a6cfbc5" integrity sha512-8LhzvcjIoqoi1TghEkRMkbbmM+jhHnBokPGkJWjclMK+Ks0MxEBow3/p2/iFTZ+OIbJHQDSfpgdZEb+af3gfVw== -"@types/bluebird@3.5.29": - version "3.5.29" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.29.tgz#7cd933c902c4fc83046517a1bef973886d00bdb6" - integrity sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw== - "@types/boom@*", "@types/boom@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/boom/-/boom-7.2.0.tgz#19c36cbb5811a7493f0f2e37f31d42b28df1abc1" @@ -4762,15 +4752,7 @@ resolved "https://registry.yarnpkg.com/@types/catbox/-/catbox-10.0.1.tgz#266679017749041fe9873fee1131dd2aaa04a07e" integrity sha512-ECuJ+f5gGHiLeiE4RlE/xdqv/0JVDToegPV1aTb10tQStYa0Ycq2OJfQukDv3IFaw3B+CMV46jHc5bXe6QXEQg== -"@types/chai-jquery@1.1.40": - version "1.1.40" - resolved "https://registry.yarnpkg.com/@types/chai-jquery/-/chai-jquery-1.1.40.tgz#445bedcbbb2ae4e3027f46fa2c1733c43481ffa1" - integrity sha512-mCNEZ3GKP7T7kftKeIs7QmfZZQM7hslGSpYzKbOlR2a2HCFf9ph4nlMRA9UnuOETeOQYJVhJQK7MwGqNZVyUtQ== - dependencies: - "@types/chai" "*" - "@types/jquery" "*" - -"@types/chai@*", "@types/chai@4.2.7", "@types/chai@^4.2.11": +"@types/chai@^4.2.11": version "4.2.11" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.11.tgz#d3614d6c5f500142358e6ed24e1bf16657536c50" integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw== @@ -5260,7 +5242,7 @@ resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.6.1.tgz#325486a397504f8e22c8c551dc8b0e1d41d5d5ae" integrity sha512-JxZ0NP8NuB0BJOXi1KvAA6rySLTPmhOy4n2gzSFq/IFM3LNFm0h+2Vn/bPPgEYlWqzS2NPeLgKqfm75baX+Hog== -"@types/jquery@*", "@types/jquery@3.3.31", "@types/jquery@^3.3.31": +"@types/jquery@*", "@types/jquery@^3.3.31": version "3.3.31" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.31.tgz#27c706e4bf488474e1cb54a71d8303f37c93451b" integrity sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg== @@ -5346,11 +5328,6 @@ "@types/node" "*" "@types/webpack" "*" -"@types/lodash@4.14.149", "@types/lodash@^4.14.155": - version "4.14.156" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.156.tgz#cbe30909c89a1feeb7c60803e785344ea0ec82d1" - integrity sha512-l2AgHXcKUwx2DsvP19wtRPqZ4NkONjmorOdq4sMcxIjqdIuuV/ULo2ftuv4NUpevwfW7Ju/UKLqo0ZXuEt/8lQ== - "@types/lodash@^3.10.1": version "3.10.3" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-3.10.3.tgz#aaddec6a3c93bf03b402db3acf5d4c77bce8bdff" @@ -5361,6 +5338,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.150.tgz#649fe44684c3f1fcb6164d943c5a61977e8cf0bd" integrity sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w== +"@types/lodash@^4.14.155": + version "4.14.156" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.156.tgz#cbe30909c89a1feeb7c60803e785344ea0ec82d1" + integrity sha512-l2AgHXcKUwx2DsvP19wtRPqZ4NkONjmorOdq4sMcxIjqdIuuV/ULo2ftuv4NUpevwfW7Ju/UKLqo0ZXuEt/8lQ== + "@types/log-symbols@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/log-symbols/-/log-symbols-2.0.0.tgz#7919e2ec3c8d13879bfdcab310dd7a3f7fc9466d" @@ -5419,7 +5401,7 @@ dependencies: "@types/mime-db" "*" -"@types/minimatch@*", "@types/minimatch@3.0.3", "@types/minimatch@^3.0.3": +"@types/minimatch@*", "@types/minimatch@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== @@ -5441,11 +5423,6 @@ dependencies: "@types/node" "*" -"@types/mocha@5.2.7": - version "5.2.7" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" - integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== - "@types/mocha@^7.0.2": version "7.0.2" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" @@ -5859,32 +5836,12 @@ dependencies: "@types/node" "*" -"@types/sinon-chai@3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.3.tgz#afe392303dda95cc8069685d1e537ff434fa506e" - integrity sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ== - dependencies: - "@types/chai" "*" - "@types/sinon" "*" - -"@types/sinon@*": - version "9.0.4" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1" - integrity sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw== - dependencies: - "@types/sinonjs__fake-timers" "*" - -"@types/sinon@7.5.1": - version "7.5.1" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.5.1.tgz#d27b81af0d1cfe1f9b24eebe7a24f74ae40f5b7c" - integrity sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ== - "@types/sinon@^7.0.13": version "7.0.13" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.13.tgz#ca039c23a9e27ebea53e0901ef928ea2a1a6d313" integrity sha512-d7c/C/+H/knZ3L8/cxhicHUiTDxdgap0b/aNJfsmLwFu/iOP17mdgbQsbHA3SJmrzsjD0l3UEE5SN4xxuz5ung== -"@types/sinonjs__fake-timers@*": +"@types/sinonjs__fake-timers@6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e" integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA== @@ -7378,10 +7335,10 @@ aproba@^1.0.3, aproba@^1.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -arch@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e" - integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg== +arch@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.2.tgz#0c52bbe7344bb4fa260c443d2cbad9c00ff2f0bf" + integrity sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ== archiver-utils@^2.1.0: version "2.1.0" @@ -7849,7 +7806,7 @@ async@^2.6.3: dependencies: lodash "^4.17.14" -async@^3.1.0: +async@^3.1.0, async@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== @@ -10499,10 +10456,10 @@ commander@3.0.2: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== -commander@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83" - integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw== +commander@4.1.1, commander@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0: version "2.20.0" @@ -10524,11 +10481,6 @@ commander@^3.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.0.tgz#0641ea00838c7a964627f04cddc336a2deddd60a" integrity sha512-pl3QrGOBa9RZaslQiqnnKX2J068wcQw7j9AIaBQ9/JEp5RY6je4jKTImg0Bd+rpoONSe7GUFSgkxLeo17m3Pow== -commander@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - commander@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.0.0.tgz#dbf1909b49e5044f8fdaf0adc809f0c0722bdfd0" @@ -11489,48 +11441,39 @@ cypress-multi-reporters@^1.2.3: debug "^4.1.1" lodash "^4.17.11" -cypress@4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.5.0.tgz#01940d085f6429cec3c87d290daa47bb976a7c7b" - integrity sha512-2A4g5FW5d2fHzq8HKUGAMVTnW6P8nlWYQALiCoGN4bqBLvgwhYM/oG9oKc2CS6LnvgHFiKivKzpm9sfk3uU3zQ== +cypress@4.11.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.11.0.tgz#054b0b85fd3aea793f186249ee1216126d5f0a7e" + integrity sha512-6Yd598+KPATM+dU1Ig0g2hbA+R/o1MAKt0xIejw4nZBVLSplCouBzqeKve6XsxGU6n4HMSt/+QYsWfFcoQeSEw== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" "@cypress/request" "2.88.5" "@cypress/xvfb" "1.2.4" - "@types/blob-util" "1.3.3" - "@types/bluebird" "3.5.29" - "@types/chai" "4.2.7" - "@types/chai-jquery" "1.1.40" - "@types/jquery" "3.3.31" - "@types/lodash" "4.14.149" - "@types/minimatch" "3.0.3" - "@types/mocha" "5.2.7" - "@types/sinon" "7.5.1" - "@types/sinon-chai" "3.2.3" + "@types/sinonjs__fake-timers" "6.0.1" "@types/sizzle" "2.3.2" - arch "2.1.1" + arch "2.1.2" bluebird "3.7.2" cachedir "2.3.0" chalk "2.4.2" check-more-types "2.24.0" cli-table3 "0.5.1" - commander "4.1.0" + commander "4.1.1" common-tags "1.8.0" debug "4.1.1" - eventemitter2 "4.1.2" + eventemitter2 "6.4.2" execa "1.0.0" executable "4.1.1" extract-zip "1.7.0" fs-extra "8.1.0" - getos "3.1.4" + getos "3.2.1" is-ci "2.0.0" - is-installed-globally "0.1.0" + is-installed-globally "0.3.2" lazy-ass "1.6.0" listr "0.14.3" - lodash "4.17.15" + lodash "4.17.19" log-symbols "3.0.0" minimist "1.2.5" - moment "2.24.0" + moment "2.26.0" ospath "1.2.2" pretty-bytes "5.3.0" ramda "0.26.1" @@ -13890,10 +13833,10 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter2@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-4.1.2.tgz#0e1a8477af821a6ef3995b311bf74c23a5247f15" - integrity sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU= +eventemitter2@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.2.tgz#f31f8b99d45245f0edbc5b00797830ff3b388970" + integrity sha512-r/Pwupa5RIzxIHbEKCkNXqpEQIIT4uQDxmP4G/Lug/NokVUWj0joz/WzWl3OxRpC5kDrH/WdiUJoR+IrwvXJEw== eventemitter2@~0.4.13: version "0.4.14" @@ -15515,12 +15458,12 @@ getopts@^2.2.5: resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b" integrity sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA== -getos@3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/getos/-/getos-3.1.4.tgz#29cdf240ed10a70c049add7b6f8cb08c81876faf" - integrity sha512-UORPzguEB/7UG5hqiZai8f0vQ7hzynMQyJLxStoQ8dPGAcmgsfXOPA4iE/fGtweHYkK+z4zc9V0g+CIFRf5HYw== +getos@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" + integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== dependencies: - async "^3.1.0" + async "^3.2.0" getos@^3.1.0: version "3.1.0" @@ -18256,15 +18199,7 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69" integrity sha1-bghLvJIGH7sJcexYts5tQE4k2mk= -is-installed-globally@0.1.0, is-installed-globally@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= - dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" - -is-installed-globally@^0.3.1: +is-installed-globally@0.3.2, is-installed-globally@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== @@ -18272,6 +18207,14 @@ is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" +is-installed-globally@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= + dependencies: + global-dirs "^0.1.0" + is-path-inside "^1.0.0" + is-integer@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/is-integer/-/is-integer-1.0.7.tgz#6bde81aacddf78b659b6629d629cadc51a886d5c" @@ -20799,16 +20742,16 @@ lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@4.17.19, lodash@^4.17.16: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + lodash@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y= -lodash@^4.17.16: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== - "lodash@npm:@elastic/lodash@3.10.1-kibana4": version "3.10.1-kibana4" resolved "https://registry.yarnpkg.com/@elastic/lodash/-/lodash-3.10.1-kibana4.tgz#d491228fd659b4a1b0dfa08ba9c67a4979b9746d" @@ -21974,7 +21917,12 @@ moment-timezone@^0.5.27: dependencies: moment ">= 2.9.0" -moment@2.24.0, "moment@>= 2.9.0", moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.24.0: +moment@2.26.0: + version "2.26.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a" + integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw== + +"moment@>= 2.9.0", moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== From 7b29ecf0b51a835394b0c45fe0623cc978455520 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 28 Jul 2020 10:29:33 +0300 Subject: [PATCH 18/75] [Functional Tests] Fix flakiness on TSVB chart on switching index patterns test (#73238) --- test/functional/services/combo_box.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/services/combo_box.ts b/test/functional/services/combo_box.ts index 60fea7ea86cf9..ac7a40361d065 100644 --- a/test/functional/services/combo_box.ts +++ b/test/functional/services/combo_box.ts @@ -90,7 +90,7 @@ export function ComboBoxProvider({ getService, getPageObjects }: FtrProviderCont await this.clickOption(options.clickWithMouse, selectOptions[0]); } else { // if it doesn't find the item which text starts with value, it will choose the first option - const firstOption = await find.byCssSelector('.euiFilterSelectItem'); + const firstOption = await find.byCssSelector('.euiFilterSelectItem', 5000); await this.clickOption(options.clickWithMouse, firstOption); } } else { From a696f6c79b3fe20d60712faeb21e31d1f4538de4 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 28 Jul 2020 10:29:47 +0300 Subject: [PATCH 19/75] [Functional Tests] Increase waitTime for timelion to fetch the results (#73255) Co-authored-by: Elastic Machine --- test/functional/page_objects/timelion_page.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/page_objects/timelion_page.ts b/test/functional/page_objects/timelion_page.ts index f025fc946bef1..23a9cc514a444 100644 --- a/test/functional/page_objects/timelion_page.ts +++ b/test/functional/page_objects/timelion_page.ts @@ -47,7 +47,7 @@ export function TimelionPageProvider({ getService, getPageObjects }: FtrProvider public async updateExpression(updates: string) { const input = await testSubjects.find('timelionExpressionTextArea'); await input.type(updates); - await PageObjects.common.sleep(500); + await PageObjects.common.sleep(1000); } public async getExpression() { @@ -60,7 +60,7 @@ export function TimelionPageProvider({ getService, getPageObjects }: FtrProvider return await Promise.all(elements.map(async (element) => await element.getVisibleText())); } - public async clickSuggestion(suggestionIndex = 0, waitTime = 500) { + public async clickSuggestion(suggestionIndex = 0, waitTime = 1000) { const elements = await testSubjects.findAll('timelionSuggestionListItem'); if (suggestionIndex > elements.length) { throw new Error( From 9b570a9bf1262428661695179fee801345017efc Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 28 Jul 2020 09:46:36 +0200 Subject: [PATCH 20/75] fix dashboard index pattern race condition (#72899) * fix dashboard index pattern race condition * improve Co-authored-by: Elastic Machine --- .../application/dashboard_app_controller.tsx | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 8138e1c7f4dfd..2a0e2889575f3 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -25,8 +25,8 @@ import React, { useState, ReactElement } from 'react'; import ReactDOM from 'react-dom'; import angular from 'angular'; -import { Subscription } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { Observable, pipe, Subscription } from 'rxjs'; +import { filter, map, mapTo, startWith, switchMap } from 'rxjs/operators'; import { History } from 'history'; import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public'; import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; @@ -253,11 +253,7 @@ export class DashboardAppController { navActions[TopNavIds.VISUALIZE](); }; - const updateIndexPatterns = (container?: DashboardContainer) => { - if (!container || isErrorEmbeddable(container)) { - return; - } - + function getDashboardIndexPatterns(container: DashboardContainer): IndexPattern[] { let panelIndexPatterns: IndexPattern[] = []; Object.values(container.getChildIds()).forEach((id) => { const embeddableInstance = container.getChild(id); @@ -267,19 +263,34 @@ export class DashboardAppController { panelIndexPatterns.push(...embeddableIndexPatterns); }); panelIndexPatterns = uniqBy(panelIndexPatterns, 'id'); + return panelIndexPatterns; + } - if (panelIndexPatterns && panelIndexPatterns.length > 0) { - $scope.$evalAsync(() => { - $scope.indexPatterns = panelIndexPatterns; - }); - } else { - indexPatterns.getDefault().then((defaultIndexPattern) => { - $scope.$evalAsync(() => { - $scope.indexPatterns = [defaultIndexPattern as IndexPattern]; - }); + const updateIndexPatternsOperator = pipe( + filter((container: DashboardContainer) => !!container && !isErrorEmbeddable(container)), + map(getDashboardIndexPatterns), + // using switchMap for previous task cancellation + switchMap((panelIndexPatterns: IndexPattern[]) => { + return new Observable((observer) => { + if (panelIndexPatterns && panelIndexPatterns.length > 0) { + $scope.$evalAsync(() => { + if (observer.closed) return; + $scope.indexPatterns = panelIndexPatterns; + observer.complete(); + }); + } else { + indexPatterns.getDefault().then((defaultIndexPattern) => { + if (observer.closed) return; + $scope.$evalAsync(() => { + if (observer.closed) return; + $scope.indexPatterns = [defaultIndexPattern as IndexPattern]; + observer.complete(); + }); + }); + } }); - } - }; + }) + ); const getEmptyScreenProps = ( shouldShowEditHelp: boolean, @@ -384,11 +395,17 @@ export class DashboardAppController { ) : null; }; - updateIndexPatterns(dashboardContainer); - - outputSubscription = dashboardContainer.getOutput$().subscribe(() => { - updateIndexPatterns(dashboardContainer); - }); + outputSubscription = new Subscription(); + outputSubscription.add( + dashboardContainer + .getOutput$() + .pipe( + mapTo(dashboardContainer), + startWith(dashboardContainer), // to trigger initial index pattern update + updateIndexPatternsOperator + ) + .subscribe() + ); inputSubscription = dashboardContainer.getInput$().subscribe(() => { let dirty = false; From abfda1f79273111b581a14b1a43fe134c6053e6c Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 28 Jul 2020 09:57:04 +0200 Subject: [PATCH 21/75] Use "Apply_filter_trigger" in dashboard drilldown (#71468) * attach dashboard drilldown to apply filter trigger * fix types Co-authored-by: Elastic Machine --- ...na-plugin-plugins-data-public.esfilters.md | 1 + src/plugins/dashboard/public/index.ts | 6 +- src/plugins/dashboard/public/plugin.tsx | 7 +- src/plugins/dashboard/public/url_generator.ts | 6 +- src/plugins/data/public/index.ts | 2 + src/plugins/data/public/public.api.md | 94 ++++++------- .../data/public/query/timefilter/index.ts | 2 +- .../timefilter/lib/extract_time_filter.ts | 15 ++- x-pack/plugins/dashboard_enhanced/kibana.json | 3 +- .../flyout_create_drilldown.tsx | 11 +- .../constants.ts | 7 + .../drilldown.test.tsx | 54 ++------ .../drilldown.tsx | 125 +++++++----------- .../dashboard_to_dashboard_drilldown/index.ts | 5 +- .../dashboard_to_dashboard_drilldown/types.ts | 10 -- .../embeddable_action_storage.test.ts | 41 ++++++ .../embeddables/embeddable_action_storage.ts | 30 ++++- .../connected_flyout_manage_drilldowns.tsx | 6 +- 18 files changed, 227 insertions(+), 198 deletions(-) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md index 37142cf1794c3..bc34d4113f847 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md @@ -52,5 +52,6 @@ esFilters: { convertRangeFilterToTimeRangeString: typeof convertRangeFilterToTimeRangeString; mapAndFlattenFilters: (filters: import("../common").Filter[]) => import("../common").Filter[]; extractTimeFilter: typeof extractTimeFilter; + extractTimeRange: typeof extractTimeRange; } ``` diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index 17968dd0281e6..dcfde67cd9f13 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -32,7 +32,11 @@ export { export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; export { DashboardStart, DashboardUrlGenerator } from './plugin'; -export { DASHBOARD_APP_URL_GENERATOR, createDashboardUrlGenerator } from './url_generator'; +export { + DASHBOARD_APP_URL_GENERATOR, + createDashboardUrlGenerator, + DashboardUrlGeneratorState, +} from './url_generator'; export { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; export { SavedObjectDashboard } from './saved_dashboards'; export { SavedDashboardPanel } from './types'; diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 041a02a251e8a..f0b57fec169fd 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -65,6 +65,7 @@ import { ACTION_REPLACE_PANEL, ClonePanelAction, ClonePanelActionContext, + createDashboardContainerByValueRenderer, DASHBOARD_CONTAINER_TYPE, DashboardContainerFactory, DashboardContainerFactoryDefinition, @@ -77,17 +78,17 @@ import { import { createDashboardUrlGenerator, DASHBOARD_APP_URL_GENERATOR, - DashboardAppLinkGeneratorState, + DashboardUrlGeneratorState, } from './url_generator'; import { createSavedDashboardLoader } from './saved_dashboards'; import { DashboardConstants } from './dashboard_constants'; import { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; -import { createDashboardContainerByValueRenderer } from './application'; +import { UrlGeneratorState } from '../../share/public'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { - [DASHBOARD_APP_URL_GENERATOR]: DashboardAppLinkGeneratorState; + [DASHBOARD_APP_URL_GENERATOR]: UrlGeneratorState; } } diff --git a/src/plugins/dashboard/public/url_generator.ts b/src/plugins/dashboard/public/url_generator.ts index 188de7fd857be..68a50396e00d6 100644 --- a/src/plugins/dashboard/public/url_generator.ts +++ b/src/plugins/dashboard/public/url_generator.ts @@ -26,7 +26,7 @@ import { RefreshInterval, } from '../../data/public'; import { setStateToKbnUrl } from '../../kibana_utils/public'; -import { UrlGeneratorsDefinition, UrlGeneratorState } from '../../share/public'; +import { UrlGeneratorsDefinition } from '../../share/public'; import { SavedObjectLoader } from '../../saved_objects/public'; import { ViewMode } from '../../embeddable/public'; @@ -35,7 +35,7 @@ export const GLOBAL_STATE_STORAGE_KEY = '_g'; export const DASHBOARD_APP_URL_GENERATOR = 'DASHBOARD_APP_URL_GENERATOR'; -export type DashboardAppLinkGeneratorState = UrlGeneratorState<{ +export interface DashboardUrlGeneratorState { /** * If given, the dashboard saved object with this id will be loaded. If not given, * a new, unsaved dashboard will be loaded up. @@ -79,7 +79,7 @@ export type DashboardAppLinkGeneratorState = UrlGeneratorState<{ * View mode of the dashboard. */ viewMode?: ViewMode; -}>; +} export const createDashboardUrlGenerator = ( getStartServices: () => Promise<{ diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 846471420327f..e95150e8f6f73 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -58,6 +58,7 @@ import { changeTimeFilter, mapAndFlattenFilters, extractTimeFilter, + extractTimeRange, convertRangeFilterToTimeRangeString, } from './query'; @@ -99,6 +100,7 @@ export const esFilters = { convertRangeFilterToTimeRangeString, mapAndFlattenFilters, extractTimeFilter, + extractTimeRange, }; export { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index a8868c07061c3..65670bc1cf83e 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -499,6 +499,7 @@ export const esFilters: { convertRangeFilterToTimeRangeString: typeof convertRangeFilterToTimeRangeString; mapAndFlattenFilters: (filters: import("../common").Filter[]) => import("../common").Filter[]; extractTimeFilter: typeof extractTimeFilter; + extractTimeRange: typeof extractTimeRange; }; // Warning: (ae-missing-release-tag) "esKuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1973,52 +1974,53 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/match_all_filter.ts:28:3 - (ae-forgotten-export) The symbol "MatchAllFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:371:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:372:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:381:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "extractTimeRange" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:373:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:374:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:41:60 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:54:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:55:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/query/timefilter/index.ts b/src/plugins/data/public/query/timefilter/index.ts index 19386c10ab59f..dc9a4ef8c21a6 100644 --- a/src/plugins/data/public/query/timefilter/index.ts +++ b/src/plugins/data/public/query/timefilter/index.ts @@ -23,5 +23,5 @@ export * from './types'; export { Timefilter, TimefilterContract } from './timefilter'; export { TimeHistory, TimeHistoryContract } from './time_history'; export { changeTimeFilter, convertRangeFilterToTimeRangeString } from './lib/change_time_filter'; -export { extractTimeFilter } from './lib/extract_time_filter'; +export { extractTimeFilter, extractTimeRange } from './lib/extract_time_filter'; export { validateTimeRange } from './lib/validate_timerange'; diff --git a/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts index 23dd1547baf10..2f93196e3218b 100644 --- a/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts +++ b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts @@ -18,7 +18,8 @@ */ import { keys, partition } from 'lodash'; -import { Filter, isRangeFilter, RangeFilter } from '../../../../common'; +import { Filter, isRangeFilter, RangeFilter, TimeRange } from '../../../../common'; +import { convertRangeFilterToTimeRangeString } from './change_time_filter'; export function extractTimeFilter(timeFieldName: string, filters: Filter[]) { const [timeRangeFilter, restOfFilters] = partition(filters, (obj: Filter) => { @@ -36,3 +37,15 @@ export function extractTimeFilter(timeFieldName: string, filters: Filter[]) { timeRangeFilter: timeRangeFilter[0] as RangeFilter | undefined, }; } + +export function extractTimeRange( + filters: Filter[], + timeFieldName?: string +): { restOfFilters: Filter[]; timeRange?: TimeRange } { + if (!timeFieldName) return { restOfFilters: filters, timeRange: undefined }; + const { timeRangeFilter, restOfFilters } = extractTimeFilter(timeFieldName, filters); + return { + restOfFilters, + timeRange: timeRangeFilter ? convertRangeFilterToTimeRangeString(timeRangeFilter) : undefined, + }; +} diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index ba5d8052ca787..264fa0438ea11 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -8,6 +8,7 @@ "requiredBundles": [ "kibanaUtils", "embeddableEnhanced", - "kibanaReact" + "kibanaReact", + "uiActions" ] } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx index 4804a700c6cff..2de862a6708a8 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -6,7 +6,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; +import { + ActionByType, + APPLY_FILTER_TRIGGER, + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, +} from '../../../../../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; @@ -42,7 +47,9 @@ export class FlyoutCreateDrilldownAction implements ActionByType -1; + return supportedTriggers.some((trigger) => + [VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER, APPLY_FILTER_TRIGGER].includes(trigger) + ); } public async isCompatible(context: EmbeddableContext) { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts index e2a530b156da5..daefcf2d68cc5 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts @@ -4,4 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +/** + * note: + * don't change this string without carefull consideration, + * because it is stored in saved objects. + * Also temporary dashboard drilldown migration code inside embeddable plugin relies on it + * x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts + */ export const DASHBOARD_TO_DASHBOARD_DRILLDOWN = 'DASHBOARD_TO_DASHBOARD_DRILLDOWN'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx index 52b232afa9410..40fa469feb34b 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx @@ -5,9 +5,8 @@ */ import { DashboardToDashboardDrilldown } from './drilldown'; -import { savedObjectsServiceMock, coreMock } from '../../../../../../../src/core/public/mocks'; -import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; -import { ActionContext, Config } from './types'; +import { Config } from './types'; +import { coreMock, savedObjectsServiceMock } from '../../../../../../../src/core/public/mocks'; import { Filter, FilterStateStore, @@ -15,16 +14,13 @@ import { RangeFilter, TimeRange, } from '../../../../../../../src/plugins/data/common'; -import { esFilters } from '../../../../../../../src/plugins/data/public'; - +import { + ApplyGlobalFilterActionContext, + esFilters, +} from '../../../../../../../src/plugins/data/public'; // convenient to use real implementation here. import { createDashboardUrlGenerator } from '../../../../../../../src/plugins/dashboard/public/url_generator'; import { UrlGeneratorsService } from '../../../../../../../src/plugins/share/public/url_generators'; -import { VisualizeEmbeddableContract } from '../../../../../../../src/plugins/visualizations/public'; -import { - RangeSelectContext, - ValueClickContext, -} from '../../../../../../../src/plugins/embeddable/public'; import { StartDependencies } from '../../../plugin'; import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_objects/public'; import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_utils/public/core'; @@ -82,11 +78,10 @@ describe('.execute() & getHref', () => { config: Partial, embeddableInput: { filters?: Filter[]; timeRange?: TimeRange; query?: Query }, filtersFromEvent: Filter[], - useRangeEvent = false + timeFieldName?: string ) { const navigateToApp = jest.fn(); const getUrlForApp = jest.fn((app, opt) => `${app}/${opt.path}`); - const dataPluginActions = dataPluginMock.createStartContract().actions; const savedObjectsClient = savedObjectsServiceMock.createStartContract().client; const drilldown = new DashboardToDashboardDrilldown({ @@ -102,9 +97,6 @@ describe('.execute() & getHref', () => { }, plugins: { uiActionsEnhanced: {}, - data: { - actions: dataPluginActions, - }, }, self: {}, })) as unknown) as StartServicesGetter>, @@ -119,12 +111,6 @@ describe('.execute() & getHref', () => { ) ), }); - const selectRangeFiltersSpy = jest - .spyOn(dataPluginActions, 'createFiltersFromRangeSelectAction') - .mockImplementation(() => Promise.resolve(filtersFromEvent)); - const valueClickFiltersSpy = jest - .spyOn(dataPluginActions, 'createFiltersFromValueClickAction') - .mockImplementation(() => Promise.resolve(filtersFromEvent)); const completeConfig: Config = { dashboardId: 'id', @@ -134,12 +120,7 @@ describe('.execute() & getHref', () => { }; const context = ({ - data: { - ...(useRangeEvent - ? ({ range: {} } as RangeSelectContext['data']) - : ({ data: [] } as ValueClickContext['data'])), - timeFieldName: 'order_date', - }, + filters: filtersFromEvent, embeddable: { getInput: () => ({ filters: [], @@ -148,18 +129,11 @@ describe('.execute() & getHref', () => { ...embeddableInput, }), }, - } as unknown) as ActionContext; + timeFieldName, + } as unknown) as ApplyGlobalFilterActionContext; await drilldown.execute(completeConfig, context); - if (useRangeEvent) { - expect(selectRangeFiltersSpy).toBeCalledTimes(1); - expect(valueClickFiltersSpy).toBeCalledTimes(0); - } else { - expect(selectRangeFiltersSpy).toBeCalledTimes(0); - expect(valueClickFiltersSpy).toBeCalledTimes(1); - } - expect(navigateToApp).toBeCalledTimes(1); expect(navigateToApp.mock.calls[0][0]).toBe('dashboards'); @@ -180,8 +154,7 @@ describe('.execute() & getHref', () => { dashboardId: testDashboardId, }, {}, - [], - false + [] ); expect(href).toEqual(expect.stringContaining(`view/${testDashboardId}`)); @@ -289,8 +262,7 @@ describe('.execute() & getHref', () => { to: 'now', }, }, - [], - false + [] ); expect(href).not.toEqual(expect.stringContaining('now-300m')); @@ -308,7 +280,7 @@ describe('.execute() & getHref', () => { }, }, [getMockTimeRangeFilter()], - true + getMockTimeRangeFilter().meta.key ); expect(href).not.toEqual(expect.stringContaining('now-300m')); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx index 26a69132cffb1..703acbc8d9d59 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx @@ -6,20 +6,24 @@ import React from 'react'; import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public'; -import { DashboardUrlGenerator } from '../../../../../../../src/plugins/dashboard/public'; -import { ActionContext, Config } from './types'; +import { + DashboardUrlGenerator, + DashboardUrlGeneratorState, +} from '../../../../../../../src/plugins/dashboard/public'; import { CollectConfigContainer } from './components'; import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../ui_actions_enhanced/public'; import { txtGoToDashboard } from './i18n'; -import { esFilters } from '../../../../../../../src/plugins/data/public'; -import { VisualizeEmbeddableContract } from '../../../../../../../src/plugins/visualizations/public'; import { - isRangeSelectTriggerContext, - isValueClickTriggerContext, -} from '../../../../../../../src/plugins/embeddable/public'; + ApplyGlobalFilterActionContext, + esFilters, + isFilters, + isQuery, + isTimeRange, +} from '../../../../../../../src/plugins/data/public'; import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_utils/public'; import { StartDependencies } from '../../../plugin'; +import { Config } from './types'; export interface Params { start: StartServicesGetter>; @@ -27,7 +31,7 @@ export interface Params { } export class DashboardToDashboardDrilldown - implements Drilldown> { + implements Drilldown { constructor(protected readonly params: Params) {} public readonly id = DASHBOARD_TO_DASHBOARD_DRILLDOWN; @@ -57,15 +61,12 @@ export class DashboardToDashboardDrilldown public readonly getHref = async ( config: Config, - context: ActionContext + context: ApplyGlobalFilterActionContext ): Promise => { return this.getDestinationUrl(config, context); }; - public readonly execute = async ( - config: Config, - context: ActionContext - ) => { + public readonly execute = async (config: Config, context: ApplyGlobalFilterActionContext) => { const dashboardPath = await this.getDestinationUrl(config, context); const dashboardHash = dashboardPath.split('#')[1]; @@ -76,73 +77,43 @@ export class DashboardToDashboardDrilldown private getDestinationUrl = async ( config: Config, - context: ActionContext + context: ApplyGlobalFilterActionContext ): Promise => { + const state: DashboardUrlGeneratorState = { + dashboardId: config.dashboardId, + }; + + if (context.embeddable) { + const input = context.embeddable.getInput(); + if (isQuery(input.query) && config.useCurrentFilters) state.query = input.query; + + // if useCurrentDashboardDataRange is enabled, then preserve current time range + // if undefined is passed, then destination dashboard will figure out time range itself + // for brush event this time range would be overwritten + if (isTimeRange(input.timeRange) && config.useCurrentDateRange) + state.timeRange = input.timeRange; + + // if useCurrentDashboardFilters enabled, then preserve all the filters (pinned and unpinned) + // otherwise preserve only pinned + if (isFilters(input.filters)) + state.filters = config.useCurrentFilters + ? input.filters + : input.filters?.filter((f) => esFilters.isFilterPinned(f)); + } + const { - createFiltersFromRangeSelectAction, - createFiltersFromValueClickAction, - } = this.params.start().plugins.data.actions; - const { - timeRange: currentTimeRange, - query, - filters: currentFilters, - } = context.embeddable!.getInput(); - - // if useCurrentDashboardFilters enabled, then preserve all the filters (pinned and unpinned) - // otherwise preserve only pinned - const existingFilters = - (config.useCurrentFilters - ? currentFilters - : currentFilters?.filter((f) => esFilters.isFilterPinned(f))) ?? []; - - // if useCurrentDashboardDataRange is enabled, then preserve current time range - // if undefined is passed, then destination dashboard will figure out time range itself - // for brush event this time range would be overwritten - let timeRange = config.useCurrentDateRange ? currentTimeRange : undefined; - let filtersFromEvent = await (async () => { - try { - if (isRangeSelectTriggerContext(context)) - return await createFiltersFromRangeSelectAction(context.data); - if (isValueClickTriggerContext(context)) - return await createFiltersFromValueClickAction(context.data); - - // eslint-disable-next-line no-console - console.warn( - ` - DashboardToDashboard drilldown: can't extract filters from action. - Is it not supported action?`, - context - ); - - return []; - } catch (e) { - // eslint-disable-next-line no-console - console.warn( - ` - DashboardToDashboard drilldown: error extracting filters from action. - Continuing without applying filters from event`, - e - ); - return []; - } - })(); - - if (context.data.timeFieldName) { - const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( - context.data.timeFieldName, - filtersFromEvent - ); - filtersFromEvent = restOfFilters; - if (timeRangeFilter) { - timeRange = esFilters.convertRangeFilterToTimeRangeString(timeRangeFilter); - } + restOfFilters: filtersFromEvent, + timeRange: timeRangeFromEvent, + } = esFilters.extractTimeRange(context.filters, context.timeFieldName); + + if (filtersFromEvent) { + state.filters = [...(state.filters ?? []), ...filtersFromEvent]; } - return this.params.getDashboardUrlGenerator().createUrl({ - dashboardId: config.dashboardId, - query: config.useCurrentFilters ? query : undefined, - timeRange, - filters: [...existingFilters, ...filtersFromEvent], - }); + if (timeRangeFromEvent) { + state.timeRange = timeRangeFromEvent; + } + + return this.params.getDashboardUrlGenerator().createUrl(state); }; } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts index 914f34980a272..49065a96b4f7b 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts @@ -9,7 +9,4 @@ export { DashboardToDashboardDrilldown, Params as DashboardToDashboardDrilldownParams, } from './drilldown'; -export { - ActionContext as DashboardToDashboardActionContext, - Config as DashboardToDashboardConfig, -} from './types'; +export { Config } from './types'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts index 6be2e2a77269f..426e250499de0 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts @@ -4,16 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - ValueClickContext, - RangeSelectContext, - IEmbeddable, -} from '../../../../../../../src/plugins/embeddable/public'; - -export type ActionContext = - | ValueClickContext - | RangeSelectContext; - export interface Config { dashboardId?: string; useCurrentFilters: boolean; diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts index 5c5d98d75295d..fffb75451f8ac 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts @@ -11,6 +11,9 @@ import { } from './embeddable_action_storage'; import { UiActionsEnhancedSerializedEvent } from '../../../ui_actions_enhanced/public'; import { of } from '../../../../../src/plugins/kibana_utils/public'; +// use real const to make test fail in case someone accidentally changes it +import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from '../../../dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown'; +import { APPLY_FILTER_TRIGGER } from '../../../../../src/plugins/ui_actions/public'; class TestEmbeddable extends Embeddable { public readonly type = 'test'; @@ -539,4 +542,42 @@ describe('EmbeddableActionStorage', () => { expect(await storage.list()).toEqual([]); }); }); + + describe('migrate', () => { + test('DASHBOARD_TO_DASHBOARD_DRILLDOWN triggers migration', async () => { + const embeddable = new TestEmbeddable(); + const OTHER_TRIGGER = 'OTHER_TRIGGER'; + embeddable.updateInput({ + enhancements: { + dynamicActions: { + events: [ + { + eventId: '1', + triggers: [OTHER_TRIGGER], + action: { + factoryId: DASHBOARD_TO_DASHBOARD_DRILLDOWN, + name: '', + config: {}, + }, + }, + { + eventId: '2', + triggers: [OTHER_TRIGGER], + action: { + factoryId: 'SOME_OTHER', + name: '', + config: {}, + }, + }, + ], + }, + }, + }); + const storage = new EmbeddableActionStorage(embeddable); + + const [event1, event2] = await storage.list(); + expect(event1.triggers).toEqual([APPLY_FILTER_TRIGGER]); + expect(event2.triggers).toEqual([OTHER_TRIGGER]); + }); + }); }); diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts index fdc42585a80ce..8881b2063c8db 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts @@ -46,7 +46,7 @@ export class EmbeddableActionStorage extends AbstractActionStorage { public async create(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = input.enhancements?.dynamicActions?.events || []; + const events = this.getEventsFromEmbeddable(); const exists = !!events.find(({ eventId }) => eventId === event.eventId); if (exists) { @@ -61,7 +61,7 @@ export class EmbeddableActionStorage extends AbstractActionStorage { public async update(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = input.enhancements?.dynamicActions?.events || []; + const events = this.getEventsFromEmbeddable(); const index = events.findIndex(({ eventId }) => eventId === event.eventId); if (index === -1) { @@ -77,7 +77,7 @@ export class EmbeddableActionStorage extends AbstractActionStorage { public async remove(eventId: string) { const input = this.embbeddable.getInput(); - const events = input.enhancements?.dynamicActions?.events || []; + const events = this.getEventsFromEmbeddable(); const index = events.findIndex((event) => eventId === event.eventId); if (index === -1) { @@ -93,7 +93,7 @@ export class EmbeddableActionStorage extends AbstractActionStorage { public async read(eventId: string): Promise { const input = this.embbeddable.getInput(); - const events = input.enhancements?.dynamicActions?.events || []; + const events = this.getEventsFromEmbeddable(); const event = events.find((ev) => eventId === ev.eventId); if (!event) { @@ -107,8 +107,28 @@ export class EmbeddableActionStorage extends AbstractActionStorage { } public async list(): Promise { + return this.getEventsFromEmbeddable(); + } + + private getEventsFromEmbeddable() { const input = this.embbeddable.getInput(); const events = input.enhancements?.dynamicActions?.events || []; - return events; + return this.migrate(events); + } + + // TODO: https://github.com/elastic/kibana/issues/71431 + // Migration implementation should use registry + // Action factories implementations should register own migrations + private migrate(events: SerializedEvent[]): SerializedEvent[] { + return events.map((event) => { + // Initially dashboard drilldown relied on VALUE_CLICK & RANGE_SELECT + if (event.action.factoryId === 'DASHBOARD_TO_DASHBOARD_DRILLDOWN') { + return { + ...event, + triggers: ['FILTER_TRIGGER'], + }; + } + return event; + }); } } diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 20d15b4f4d2bd..283464b137ff9 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -11,9 +11,8 @@ import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldow import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/public'; import { - VALUE_CLICK_TRIGGER, - SELECT_RANGE_TRIGGER, TriggerContextMapping, + APPLY_FILTER_TRIGGER, } from '../../../../../../../src/plugins/ui_actions/public'; import { useContainerState } from '../../../../../../../src/plugins/kibana_utils/public'; import { DrilldownListItem } from '../list_manage_drilldowns'; @@ -67,8 +66,9 @@ export function createFlyoutManageDrilldowns({ return (props: ConnectedFlyoutManageDrilldownsProps) => { const isCreateOnly = props.viewMode === 'create'; + // TODO: https://github.com/elastic/kibana/issues/59569 const selectedTriggers: Array = React.useMemo( - () => [VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER], + () => [APPLY_FILTER_TRIGGER], [] ); From 5ea28702f6a2aa3e0592a79fa5ea396ff68fd972 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 28 Jul 2020 11:15:58 +0300 Subject: [PATCH 22/75] [Functional Tests] Increase the timeout when locating the tableview] (#73243) --- test/functional/page_objects/visual_builder_page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 0db8cac0f0758..8488eb8cd2749 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -408,7 +408,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro * @memberof VisualBuilderPage */ public async getViewTable(): Promise { - const tableView = await testSubjects.find('tableView'); + const tableView = await testSubjects.find('tableView', 20000); return await tableView.getVisibleText(); } From c0826a32730ba55c0192e81fc23788be5966fdcd Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 28 Jul 2020 11:37:37 +0300 Subject: [PATCH 23/75] Fix App status flaky test (#72853) * wait for link to be updated * await, please! Co-authored-by: Elastic Machine --- .../plugins/core_app_status/public/plugin.tsx | 3 +-- .../core_plugins/application_status.ts | 16 +++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/test/plugin_functional/plugins/core_app_status/public/plugin.tsx b/test/plugin_functional/plugins/core_app_status/public/plugin.tsx index af23bfbe1f8f5..bdc08c03c1912 100644 --- a/test/plugin_functional/plugins/core_app_status/public/plugin.tsx +++ b/test/plugin_functional/plugins/core_app_status/public/plugin.tsx @@ -26,6 +26,7 @@ import { CoreStart, AppMountParameters, } from 'kibana/public'; +import { renderApp } from './application'; import './types'; export class CoreAppStatusPlugin implements Plugin<{}, CoreAppStatusPluginStart> { @@ -36,7 +37,6 @@ export class CoreAppStatusPlugin implements Plugin<{}, CoreAppStatusPluginStart> id: 'app_status_start', title: 'App Status Start Page', async mount(params: AppMountParameters) { - const { renderApp } = await import('./application'); return renderApp('app_status_start', params); }, }); @@ -47,7 +47,6 @@ export class CoreAppStatusPlugin implements Plugin<{}, CoreAppStatusPluginStart> euiIconType: 'snowflake', updater$: this.appUpdater, async mount(params: AppMountParameters) { - const { renderApp } = await import('./application'); return renderApp('app_status', params); }, }); diff --git a/test/plugin_functional/test_suites/core_plugins/application_status.ts b/test/plugin_functional/test_suites/core_plugins/application_status.ts index 31a1c28b50842..a4c2db733b894 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_status.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_status.ts @@ -41,6 +41,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const PageObjects = getPageObjects(['common']); const browser = getService('browser'); const appsMenu = getService('appsMenu'); + const retry = getService('retry'); const testSubjects = getService('testSubjects'); const setAppStatus = async (s: Partial) => { @@ -50,15 +51,14 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }, s); }; - const navigateToApp = async (i: string) => { + const navigateToApp = async (id: string) => { return await browser.executeAsync(async (appId, cb) => { await window.__coreAppStatus.navigateToApp(appId); cb(); - }, i); + }, id); }; - // FLAKY: https://github.com/elastic/kibana/issues/65423 - describe.skip('application status management', () => { + describe('application status management', () => { beforeEach(async () => { await PageObjects.common.navigateToApp('app_status_start'); }); @@ -101,15 +101,17 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); it('allows to change the defaultPath of an application', async () => { - let link = await appsMenu.getLink('App Status'); + const link = await appsMenu.getLink('App Status'); expect(link!.href).to.eql(getKibanaUrl('/app/app_status')); await setAppStatus({ defaultPath: '/arbitrary/path', }); - link = await appsMenu.getLink('App Status'); - expect(link!.href).to.eql(getKibanaUrl('/app/app_status/arbitrary/path')); + await retry.waitFor('link url updated with "defaultPath"', async () => { + const updatedLink = await appsMenu.getLink('App Status'); + return updatedLink?.href === getKibanaUrl('/app/app_status/arbitrary/path'); + }); await navigateToApp('app_status'); expect(await testSubjects.exists('appStatusApp')).to.eql(true); From 1c791f39dac906f3a46b3703a82ba33e8f263a4b Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Tue, 28 Jul 2020 10:48:14 +0200 Subject: [PATCH 24/75] [SIEM][Timelines] Updates timeline template callout text (#73334) * updates timeline template callout text * fixes typo in constant Co-authored-by: Elastic Machine --- .../timelines/components/timeline/header/index.test.tsx | 2 +- .../public/timelines/components/timeline/header/index.tsx | 2 +- .../timelines/components/timeline/header/translations.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx index e0043f3b232da..e7b0ce7b7428e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx @@ -177,7 +177,7 @@ describe('Header', () => { expect( wrapper.find('[data-test-subj="timelineImmutableCallOut"]').first().prop('title') ).toEqual( - 'This timeline is immutable, therefore not allowed to save it within the security application, though you may continue to use the timeline to search and filter security events' + 'This prebuilt timeline template cannot be modified. To make changes, please duplicate this template and make modifications to the duplicate template.' ); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.tsx index 75bfb52f2756b..e50a6ed1e45fe 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.tsx @@ -73,7 +73,7 @@ const TimelineHeaderComponent: React.FC = ({ {status === TimelineStatus.immutable && ( Date: Tue, 28 Jul 2020 13:00:16 +0300 Subject: [PATCH 25/75] [Search] add server logs (#72454) * improve test stability * logs and scope search function * uncomment * fix ts * ts Co-authored-by: Elastic Machine --- src/plugins/data/server/plugin.ts | 4 ++-- .../es_search/es_search_strategy.test.ts | 11 +++++---- .../search/es_search/es_search_strategy.ts | 6 +++-- .../data/server/search/search_service.test.ts | 5 +++- .../data/server/search/search_service.ts | 24 +++++++++++++++---- x-pack/plugins/data_enhanced/server/plugin.ts | 12 ++++++++-- .../server/search/es_search_strategy.test.ts | 13 ++++++---- .../server/search/es_search_strategy.ts | 6 ++++- 8 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 8fa32f9bd564f..61d8e566d2d2b 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -62,11 +62,11 @@ export class DataServerPlugin implements Plugin) { - this.searchService = new SearchService(initializerContext); + this.logger = initializerContext.logger.get('data'); + this.searchService = new SearchService(initializerContext, this.logger); this.scriptsService = new ScriptsService(); this.kqlTelemetryService = new KqlTelemetryService(initializerContext); this.autocompleteService = new AutocompleteService(initializerContext); - this.logger = initializerContext.logger.get('data'); } public setup( diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts index 1155a5491e8f3..bc59bdee6a40a 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts @@ -22,6 +22,9 @@ import { pluginInitializerContextConfigMock } from '../../../../../core/server/m import { esSearchStrategyProvider } from './es_search_strategy'; describe('ES search strategy', () => { + const mockLogger: any = { + info: () => {}, + }; const mockApiCaller = jest.fn().mockResolvedValue({ _shards: { total: 10, @@ -40,14 +43,14 @@ describe('ES search strategy', () => { }); it('returns a strategy with `search`', async () => { - const esSearch = await esSearchStrategyProvider(mockConfig$); + const esSearch = await esSearchStrategyProvider(mockConfig$, mockLogger); expect(typeof esSearch.search).toBe('function'); }); it('calls the API caller with the params with defaults', async () => { const params = { index: 'logstash-*' }; - const esSearch = await esSearchStrategyProvider(mockConfig$); + const esSearch = await esSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); @@ -63,7 +66,7 @@ describe('ES search strategy', () => { it('calls the API caller with overridden defaults', async () => { const params = { index: 'logstash-*', ignoreUnavailable: false, timeout: '1000ms' }; - const esSearch = await esSearchStrategyProvider(mockConfig$); + const esSearch = await esSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); @@ -77,7 +80,7 @@ describe('ES search strategy', () => { it('returns total, loaded, and raw response', async () => { const params = { index: 'logstash-*' }; - const esSearch = await esSearchStrategyProvider(mockConfig$); + const esSearch = await esSearchStrategyProvider(mockConfig$, mockLogger); const response = await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params, diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index 82f8ef21ebb38..b8010f735c327 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -17,16 +17,18 @@ * under the License. */ import { first } from 'rxjs/operators'; -import { SharedGlobalConfig } from 'kibana/server'; +import { SharedGlobalConfig, Logger } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { Observable } from 'rxjs'; import { ISearchStrategy, getDefaultSearchParams, getTotalLoaded } from '..'; export const esSearchStrategyProvider = ( - config$: Observable + config$: Observable, + logger: Logger ): ISearchStrategy => { return { search: async (context, request, options) => { + logger.info(`search ${JSON.stringify(request.params)}`); const config = await config$.pipe(first()).toPromise(); const defaultParams = getDefaultSearchParams(config); diff --git a/src/plugins/data/server/search/search_service.test.ts b/src/plugins/data/server/search/search_service.test.ts index 8c2ed96503003..be00b7409fe4a 100644 --- a/src/plugins/data/server/search/search_service.test.ts +++ b/src/plugins/data/server/search/search_service.test.ts @@ -28,7 +28,10 @@ describe('Search service', () => { let mockCoreSetup: MockedKeys>; beforeEach(() => { - plugin = new SearchService(coreMock.createPluginInitializerContext({})); + const mockLogger: any = { + info: () => {}, + }; + plugin = new SearchService(coreMock.createPluginInitializerContext({}), mockLogger); mockCoreSetup = coreMock.createSetup(); }); diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 5686023e9a667..bbd0671754749 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -22,6 +22,7 @@ import { PluginInitializerContext, CoreSetup, RequestHandlerContext, + Logger, } from '../../../../core/server'; import { ISearchSetup, ISearchStart, ISearchStrategy } from './types'; import { registerSearchRoute } from './routes'; @@ -41,7 +42,10 @@ interface StrategyMap { export class SearchService implements Plugin { private searchStrategies: StrategyMap = {}; - constructor(private initializerContext: PluginInitializerContext) {} + constructor( + private initializerContext: PluginInitializerContext, + private readonly logger: Logger + ) {} public setup( core: CoreSetup, @@ -49,7 +53,7 @@ export class SearchService implements Plugin { ): ISearchSetup { this.registerSearchStrategy( ES_SEARCH_STRATEGY, - esSearchStrategyProvider(this.initializerContext.config.legacy.globalConfig$) + esSearchStrategyProvider(this.initializerContext.config.legacy.globalConfig$, this.logger) ); core.savedObjects.registerType(searchTelemetry); @@ -65,7 +69,11 @@ export class SearchService implements Plugin { return { registerSearchStrategy: this.registerSearchStrategy, usage }; } - private search(context: RequestHandlerContext, searchRequest: IEsSearchRequest, options: any) { + private search( + context: RequestHandlerContext, + searchRequest: IEsSearchRequest, + options: Record + ) { return this.getSearchStrategy(options.strategy || ES_SEARCH_STRATEGY).search( context, searchRequest, @@ -76,17 +84,25 @@ export class SearchService implements Plugin { public start(): ISearchStart { return { getSearchStrategy: this.getSearchStrategy, - search: this.search, + search: ( + context: RequestHandlerContext, + searchRequest: IEsSearchRequest, + options: Record + ) => { + return this.search(context, searchRequest, options); + }, }; } public stop() {} private registerSearchStrategy = (name: string, strategy: ISearchStrategy) => { + this.logger.info(`Register strategy ${name}`); this.searchStrategies[name] = strategy; }; private getSearchStrategy = (name: string): ISearchStrategy => { + this.logger.info(`Get strategy ${name}`); const strategy = this.searchStrategies[name]; if (!strategy) { throw new Error(`Search strategy ${name} not found`); diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index 4f6756231912c..9c3a0edf7e733 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -9,6 +9,7 @@ import { CoreSetup, CoreStart, Plugin, + Logger, } from '../../../../src/core/server'; import { ES_SEARCH_STRATEGY } from '../../../../src/plugins/data/common'; import { PluginSetup as DataPluginSetup } from '../../../../src/plugins/data/server'; @@ -19,12 +20,19 @@ interface SetupDependencies { } export class EnhancedDataServerPlugin implements Plugin { - constructor(private initializerContext: PluginInitializerContext) {} + private readonly logger: Logger; + + constructor(private initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get('data_enhanced'); + } public setup(core: CoreSetup, deps: SetupDependencies) { deps.data.search.registerSearchStrategy( ES_SEARCH_STRATEGY, - enhancedEsSearchStrategyProvider(this.initializerContext.config.legacy.globalConfig$) + enhancedEsSearchStrategyProvider( + this.initializerContext.config.legacy.globalConfig$, + this.logger + ) ); } diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts index 1eec941466b73..faa4f2ee499e5 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts @@ -31,6 +31,9 @@ const mockRollupResponse = { describe('ES search strategy', () => { const mockApiCaller = jest.fn(); + const mockLogger: any = { + info: () => {}, + }; const mockContext = { core: { elasticsearch: { legacy: { client: { callAsCurrentUser: mockApiCaller } } } }, }; @@ -41,7 +44,7 @@ describe('ES search strategy', () => { }); it('returns a strategy with `search`', async () => { - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); expect(typeof esSearch.search).toBe('function'); }); @@ -50,7 +53,7 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'logstash-*', body: { query: {} } }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); @@ -66,7 +69,7 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'logstash-*', body: { query: {} } }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { id: 'foo', params }); @@ -82,7 +85,7 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'foo-程', body: {} }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); @@ -97,7 +100,7 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockRollupResponse); const params = { index: 'foo-程', body: {} }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { indexType: 'rollup', diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 7b29117495a67..358335a2a4d60 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -12,6 +12,7 @@ import { LegacyAPICaller, SharedGlobalConfig, RequestHandlerContext, + Logger, } from '../../../../../src/core/server'; import { ISearchOptions, @@ -30,13 +31,15 @@ export interface AsyncSearchResponse { } export const enhancedEsSearchStrategyProvider = ( - config$: Observable + config$: Observable, + logger: Logger ): ISearchStrategy => { const search = async ( context: RequestHandlerContext, request: IEnhancedEsSearchRequest, options?: ISearchOptions ) => { + logger.info(`search ${JSON.stringify(request.params) || request.id}`); const config = await config$.pipe(first()).toPromise(); const caller = context.core.elasticsearch.legacy.client.callAsCurrentUser; const defaultParams = getDefaultSearchParams(config); @@ -48,6 +51,7 @@ export const enhancedEsSearchStrategyProvider = ( }; const cancel = async (context: RequestHandlerContext, id: string) => { + logger.info(`cancel ${id}`); const method = 'DELETE'; const path = encodeURI(`/_async_search/${id}`); await context.core.elasticsearch.legacy.client.callAsCurrentUser('transport.request', { From 12d5b8d2f95cc085065def98e614590828adfa7e Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Tue, 28 Jul 2020 13:13:01 +0200 Subject: [PATCH 26/75] executes cypress tests when there is a change in parts of alerting team code we use (#73256) --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 69c61b5bfa988..818ba748ee165 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -43,7 +43,7 @@ kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true, setCommitStatus: true) 'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'), 'xpack-savedObjectsFieldMetrics': kibanaPipeline.functionalTestProcess('xpack-savedObjectsFieldMetrics', './test/scripts/jenkins_xpack_saved_objects_field_metrics.sh'), 'xpack-securitySolutionCypress': { processNumber -> - whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/']) { + whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/', 'x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/', 'x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx']) { kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')(processNumber) } }, From 46fb8475f382cff56d10783798d6a3c2d1f3dda2 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 28 Jul 2020 14:38:14 +0300 Subject: [PATCH 27/75] [Security Solutions] Show popovers inside modals (#73264) --- .../security_solution/public/common/components/page/index.tsx | 4 ++-- .../public/common/components/with_hover_actions/index.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page/index.tsx index 9a5654ed6475f..8737fa95c94a2 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/index.tsx @@ -49,8 +49,8 @@ export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimar border: none; } - /* hide open popovers when a modal is being displayed to prevent them from covering the modal */ - body.euiBody-hasOverlayMask .euiPopover__panel-isOpen { + /* hide open draggable popovers when a modal is being displayed to prevent them from covering the modal */ + body.euiBody-hasOverlayMask .withHoverActions__popover.euiPopover__panel-isOpen{ visibility: hidden !important; } diff --git a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx index e6577bd040e25..9e28345ffbbcf 100644 --- a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx @@ -90,6 +90,7 @@ export const WithHoverActions = React.memo( hasArrow={false} isOpen={isOpen} panelPaddingSize={!alwaysShow ? 's' : 'none'} + panelClassName="withHoverActions__popover" > {isOpen ? <>{hoverContent} : null} From 09b11b61f0fefb736847f5000713bcf6ebfae0b0 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 28 Jul 2020 07:44:37 -0400 Subject: [PATCH 28/75] Introduce reserved ml privilege for the apm_user role (#72266) Co-authored-by: Elastic Machine --- x-pack/plugins/infra/server/features.ts | 12 ++++----- .../plugins/ml/common/types/capabilities.ts | 16 ++++++++++++ x-pack/plugins/ml/server/plugin.ts | 6 ++++- .../disable_ui_capabilities.test.ts | 8 +++--- .../authorization/disable_ui_capabilities.ts | 9 +++---- .../feature_privilege_builder/navlink.ts | 5 +--- .../privileges/privileges.test.ts | 25 +++---------------- .../capabilities_switcher.test.ts | 2 +- .../capabilities/capabilities_switcher.ts | 3 +-- .../apis/security/privileges.ts | 2 +- .../apis/security/privileges_basic.ts | 2 +- .../infrastructure_security.ts | 24 +++++++++--------- .../feature_controls/infrastructure_spaces.ts | 22 ++++++++-------- .../infra/feature_controls/logs_security.ts | 24 +++++++++--------- .../infra/feature_controls/logs_spaces.ts | 22 ++++++++-------- .../test/ui_capabilities/common/features.ts | 2 +- .../plugins/foo_plugin/server/index.ts | 6 ++--- .../common/nav_links_builder.ts | 13 ++++++---- .../common/services/features.ts | 2 +- .../common/services/ui_capabilities.ts | 2 +- .../security_only/tests/nav_links.ts | 2 +- 21 files changed, 102 insertions(+), 107 deletions(-) diff --git a/x-pack/plugins/infra/server/features.ts b/x-pack/plugins/infra/server/features.ts index 0de431186b151..fdbd1ec894022 100644 --- a/x-pack/plugins/infra/server/features.ts +++ b/x-pack/plugins/infra/server/features.ts @@ -17,12 +17,12 @@ export const METRICS_FEATURE = { order: 700, icon: 'metricsApp', navLinkId: 'metrics', - app: ['infra', 'kibana'], + app: ['infra', 'metrics', 'kibana'], catalogue: ['infraops'], alerting: [METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID], privileges: { all: { - app: ['infra', 'kibana'], + app: ['infra', 'metrics', 'kibana'], catalogue: ['infraops'], api: ['infra'], savedObject: { @@ -35,7 +35,7 @@ export const METRICS_FEATURE = { ui: ['show', 'configureSource', 'save', 'alerting:show'], }, read: { - app: ['infra', 'kibana'], + app: ['infra', 'metrics', 'kibana'], catalogue: ['infraops'], api: ['infra'], savedObject: { @@ -58,12 +58,12 @@ export const LOGS_FEATURE = { order: 800, icon: 'logsApp', navLinkId: 'logs', - app: ['infra', 'kibana'], + app: ['infra', 'logs', 'kibana'], catalogue: ['infralogging'], alerting: [LOG_DOCUMENT_COUNT_ALERT_TYPE_ID], privileges: { all: { - app: ['infra', 'kibana'], + app: ['infra', 'logs', 'kibana'], catalogue: ['infralogging'], api: ['infra'], savedObject: { @@ -76,7 +76,7 @@ export const LOGS_FEATURE = { ui: ['show', 'configureSource', 'save'], }, read: { - app: ['infra', 'kibana'], + app: ['infra', 'logs', 'kibana'], catalogue: ['infralogging'], api: ['infra'], alerting: { diff --git a/x-pack/plugins/ml/common/types/capabilities.ts b/x-pack/plugins/ml/common/types/capabilities.ts index 504cd28b8fa14..58a2043502d27 100644 --- a/x-pack/plugins/ml/common/types/capabilities.ts +++ b/x-pack/plugins/ml/common/types/capabilities.ts @@ -7,6 +7,10 @@ import { KibanaRequest } from 'kibana/server'; import { PLUGIN_ID } from '../constants/app'; +export const apmUserMlCapabilities = { + canGetJobs: false, +}; + export const userMlCapabilities = { canAccessML: false, // Anomaly Detection @@ -68,6 +72,7 @@ export function getDefaultCapabilities(): MlCapabilities { } export function getPluginPrivileges() { + const apmUserMlCapabilitiesKeys = Object.keys(apmUserMlCapabilities); const userMlCapabilitiesKeys = Object.keys(userMlCapabilities); const adminMlCapabilitiesKeys = Object.keys(adminMlCapabilities); const allMlCapabilitiesKeys = [...adminMlCapabilitiesKeys, ...userMlCapabilitiesKeys]; @@ -101,6 +106,17 @@ export function getPluginPrivileges() { read: savedObjects, }, }, + apmUser: { + excludeFromBasePrivileges: true, + app: [], + catalogue: [], + savedObject: { + all: [], + read: [], + }, + api: apmUserMlCapabilitiesKeys.map((k) => `ml:${k}`), + ui: apmUserMlCapabilitiesKeys, + }, }; } diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 812db744d1bda..3c3824a785032 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -75,7 +75,7 @@ export class MlServerPlugin implements Plugin { new Feature({ id: 'fooFeature', name: 'Foo Feature', - app: ['fooApp'], + app: ['fooApp', 'foo'], navLinkId: 'foo', privileges: null, }), @@ -129,7 +129,7 @@ describe('usingPrivileges', () => { new Feature({ id: 'fooFeature', name: 'Foo Feature', - app: [], + app: ['foo'], navLinkId: 'foo', privileges: null, }), @@ -262,7 +262,7 @@ describe('usingPrivileges', () => { id: 'barFeature', name: 'Bar Feature', navLinkId: 'bar', - app: [], + app: ['bar'], privileges: null, }), ], @@ -412,7 +412,7 @@ describe('all', () => { new Feature({ id: 'fooFeature', name: 'Foo Feature', - app: [], + app: ['foo'], navLinkId: 'foo', privileges: null, }), diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts index a9b3fa54d3617..c126be1b07f6e 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts @@ -18,12 +18,11 @@ export function disableUICapabilitiesFactory( logger: Logger, authz: AuthorizationServiceSetup ) { - // nav links are sourced from two places: - // 1) The `navLinkId` property. This is deprecated and will be removed (https://github.com/elastic/kibana/issues/66217) - // 2) The apps property. The Kibana Platform associates nav links to the app which registers it, in a 1:1 relationship. - // This behavior is replacing the `navLinkId` property above. + // nav links are sourced from the apps property. + // The Kibana Platform associates nav links to the app which registers it, in a 1:1 relationship. + // This behavior is replacing the `navLinkId` property. const featureNavLinkIds = features - .flatMap((feature) => [feature.navLinkId, ...feature.app]) + .flatMap((feature) => feature.app) .filter((navLinkId) => navLinkId != null); const shouldDisableFeatureUICapability = ( diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/navlink.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/navlink.ts index f25632407be86..a6e5a01c7dba8 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/navlink.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/navlink.ts @@ -9,9 +9,6 @@ import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; export class FeaturePrivilegeNavlinkBuilder extends BaseFeaturePrivilegeBuilder { public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { - const appNavLinks = feature.app.map((app) => this.actions.ui.get('navLinks', app)); - return feature.navLinkId - ? [this.actions.ui.get('navLinks', feature.navLinkId), ...appNavLinks] - : appNavLinks; + return (privilegeDefinition.app ?? []).map((app) => this.actions.ui.get('navLinks', app)); } } diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts index d8ece8f68d425..89ac73c220756 100644 --- a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts @@ -54,20 +54,8 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty('features.foo-feature', { - all: [ - actions.login, - actions.version, - actions.ui.get('navLinks', 'kibana:foo'), - actions.ui.get('navLinks', 'app-1'), - actions.ui.get('navLinks', 'app-2'), - ], - read: [ - actions.login, - actions.version, - actions.ui.get('navLinks', 'kibana:foo'), - actions.ui.get('navLinks', 'app-1'), - actions.ui.get('navLinks', 'app-2'), - ], + all: [actions.login, actions.version], + read: [actions.login, actions.version], }); }); @@ -275,7 +263,6 @@ describe('features', () => { actions.ui.get('catalogue', 'all-catalogue-2'), actions.ui.get('management', 'all-management', 'all-management-1'), actions.ui.get('management', 'all-management', 'all-management-2'), - actions.ui.get('navLinks', 'kibana:foo'), actions.savedObject.get('all-savedObject-all-1', 'bulk_get'), actions.savedObject.get('all-savedObject-all-1', 'get'), actions.savedObject.get('all-savedObject-all-1', 'find'), @@ -386,7 +373,6 @@ describe('features', () => { actions.ui.get('catalogue', 'read-catalogue-2'), actions.ui.get('management', 'read-management', 'read-management-1'), actions.ui.get('management', 'read-management', 'read-management-2'), - actions.ui.get('navLinks', 'kibana:foo'), actions.savedObject.get('read-savedObject-all-1', 'bulk_get'), actions.savedObject.get('read-savedObject-all-1', 'get'), actions.savedObject.get('read-savedObject-all-1', 'find'), @@ -644,12 +630,7 @@ describe('reserved', () => { const privileges = privilegesFactory(actions, mockXPackMainPlugin as any, mockLicenseService); const actual = privileges.get(); - expect(actual).toHaveProperty('reserved.foo', [ - actions.version, - actions.ui.get('navLinks', 'kibana:foo'), - actions.ui.get('navLinks', 'app-1'), - actions.ui.get('navLinks', 'app-2'), - ]); + expect(actual).toHaveProperty('reserved.foo', [actions.version]); }); test(`actions only specified at the privilege are alright too`, () => { diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts index 797d7fd1bdcc4..c9ea1b44e723d 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts @@ -23,7 +23,7 @@ const features = ([ id: 'feature_2', name: 'Feature 2', navLinkId: 'feature2', - app: [], + app: ['feature2'], catalogue: ['feature2Entry'], management: { kibana: ['somethingElse'], diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts index 00e2419136f48..e8d964b22010c 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts @@ -83,8 +83,7 @@ function toggleDisabledFeatures( for (const feature of disabledFeatures) { // Disable associated navLink, if one exists - const featureNavLinks = feature.navLinkId ? [feature.navLinkId, ...feature.app] : feature.app; - featureNavLinks.forEach((app) => { + feature.app.forEach((app) => { if (navLinks.hasOwnProperty(app) && !enabledAppEntries.has(app)) { navLinks[app] = false; } diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 1ad25a11be879..07233f1685385 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -43,7 +43,7 @@ export default function ({ getService }: FtrProviderContext) { }, global: ['all', 'read'], space: ['all', 'read'], - reserved: ['ml_user', 'ml_admin', 'monitoring'], + reserved: ['ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'], }; await supertest diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index d5263aed26d0b..74d95fa1e4a76 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -41,7 +41,7 @@ export default function ({ getService }: FtrProviderContext) { }, global: ['all', 'read'], space: ['all', 'read'], - reserved: ['ml_user', 'ml_admin', 'monitoring'], + reserved: ['ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'], }; await supertest diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts index 971826112a3e2..3c471516e9c66 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -423,19 +423,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(navLinks).to.not.contain(['Metrics']); }); - it(`metrics app is inaccessible and Application Not Found message is rendered`, async () => { - await PageObjects.common.navigateToApp('infraOps'); - await testSubjects.existOrFail('~appNotFoundPageContent'); - await PageObjects.common.navigateToUrlWithBrowserHistory( - 'infraOps', - '/inventory', - undefined, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } + it(`metrics app is inaccessible and returns a 404`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + }); + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) ); - await testSubjects.existOrFail('~appNotFoundPageContent'); }); }); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts index 211a9ce718b56..1bf8ded69016b 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts @@ -79,21 +79,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`metrics app is inaccessible and Application Not Found message is rendered`, async () => { - await PageObjects.common.navigateToApp('infraOps', { + await PageObjects.common.navigateToActualUrl('infraOps', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, basePath: '/s/custom_space', }); - await testSubjects.existOrFail('~appNotFoundPageContent'); - await PageObjects.common.navigateToUrlWithBrowserHistory( - 'infraOps', - '/inventory', - undefined, - { - basePath: '/s/custom_space', - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) ); - await testSubjects.existOrFail('~appNotFoundPageContent'); }); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts index c7d94f86ea420..64154ff6cf3f7 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts @@ -187,19 +187,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(navLinks).to.not.contain('Logs'); }); - it(`logs app is inaccessible and Application Not Found message is rendered`, async () => { - await PageObjects.common.navigateToApp('infraLogs'); - await testSubjects.existOrFail('~appNotFoundPageContent'); - await PageObjects.common.navigateToUrlWithBrowserHistory( - 'infraLogs', - '/stream', - undefined, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } + it(`logs app is inaccessible and returns a 404`, async () => { + await PageObjects.common.navigateToActualUrl('infraLogs', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + }); + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) ); - await testSubjects.existOrFail('~appNotFoundPageContent'); }); }); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts index 4d54539a4d09e..ea08307ccedd3 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts @@ -80,21 +80,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`logs app is inaccessible and Application Not Found message is rendered`, async () => { - await PageObjects.common.navigateToApp('infraLogs', { + await PageObjects.common.navigateToActualUrl('infraLogs', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, basePath: '/s/custom_space', }); - await testSubjects.existOrFail('~appNotFoundPageContent'); - await PageObjects.common.navigateToUrlWithBrowserHistory( - 'infraLogs', - '/stream', - undefined, - { - basePath: '/s/custom_space', - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) ); - await testSubjects.existOrFail('~appNotFoundPageContent'); }); }); }); diff --git a/x-pack/test/ui_capabilities/common/features.ts b/x-pack/test/ui_capabilities/common/features.ts index 3c015bc21e937..e3febc945c299 100644 --- a/x-pack/test/ui_capabilities/common/features.ts +++ b/x-pack/test/ui_capabilities/common/features.ts @@ -5,7 +5,7 @@ */ interface Feature { - navLinkId: string; + app: string[]; } export interface Features { diff --git a/x-pack/test/ui_capabilities/common/fixtures/plugins/foo_plugin/server/index.ts b/x-pack/test/ui_capabilities/common/fixtures/plugins/foo_plugin/server/index.ts index bff794801119a..5c80b4283a69b 100644 --- a/x-pack/test/ui_capabilities/common/fixtures/plugins/foo_plugin/server/index.ts +++ b/x-pack/test/ui_capabilities/common/fixtures/plugins/foo_plugin/server/index.ts @@ -19,11 +19,11 @@ class FooPlugin implements Plugin { name: 'Foo', icon: 'upArrow', navLinkId: 'foo_plugin', - app: ['kibana'], + app: ['foo_plugin', 'kibana'], catalogue: ['foo'], privileges: { all: { - app: ['kibana'], + app: ['foo_plugin', 'kibana'], catalogue: ['foo'], savedObject: { all: ['foo'], @@ -32,7 +32,7 @@ class FooPlugin implements Plugin { ui: ['create', 'edit', 'delete', 'show'], }, read: { - app: ['kibana'], + app: ['foo_plugin', 'kibana'], catalogue: ['foo'], savedObject: { all: [], diff --git a/x-pack/test/ui_capabilities/common/nav_links_builder.ts b/x-pack/test/ui_capabilities/common/nav_links_builder.ts index b20a499ba7e20..04ab08e08a2ba 100644 --- a/x-pack/test/ui_capabilities/common/nav_links_builder.ts +++ b/x-pack/test/ui_capabilities/common/nav_links_builder.ts @@ -13,11 +13,14 @@ export class NavLinksBuilder { ...features, // management isn't a first-class "feature", but it makes our life easier here to pretend like it is management: { - navLinkId: 'kibana:stack_management', + app: ['kibana:stack_management'], }, // TODO: Temp until navLinkIds fix is merged in appSearch: { - navLinkId: 'appSearch', + app: ['appSearch', 'workplaceSearch'], + }, + kibana: { + app: ['kibana'], }, }; } @@ -38,9 +41,9 @@ export class NavLinksBuilder { private build(callback: buildCallback): Record { const navLinks = {} as Record; for (const [featureId, feature] of Object.entries(this.features)) { - if (feature.navLinkId) { - navLinks[feature.navLinkId] = callback(featureId); - } + feature.app.forEach((app) => { + navLinks[app] = callback(featureId); + }); } return navLinks; diff --git a/x-pack/test/ui_capabilities/common/services/features.ts b/x-pack/test/ui_capabilities/common/services/features.ts index 0f796c1d0a0cc..5f6ec0ad050c7 100644 --- a/x-pack/test/ui_capabilities/common/services/features.ts +++ b/x-pack/test/ui_capabilities/common/services/features.ts @@ -40,7 +40,7 @@ export class FeaturesService { (acc: Features, feature: any) => ({ ...acc, [feature.id]: { - navLinkId: feature.navLinkId, + app: feature.app, }, }), {} diff --git a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts index bb1f3b6eefe4a..7f831973aea5c 100644 --- a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts +++ b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts @@ -52,7 +52,7 @@ export class UICapabilitiesService { }): Promise { const features = await this.featureService.get(); const applications = Object.values(features) - .map((feature) => feature.navLinkId) + .flatMap((feature) => feature.app) .filter((link) => !!link); const spaceUrlPrefix = spaceId ? `/s/${spaceId}` : ''; diff --git a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts index 18838e536cf96..d7a0dfa1cf80a 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts @@ -57,7 +57,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) { expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('navLinks'); expect(uiCapabilities.value!.navLinks).to.eql( - navLinksBuilder.only('management', 'foo') + navLinksBuilder.only('management', 'foo', 'kibana') ); break; case 'legacy_all': From b5a920d8c9cf94a1468d9f9cb022f716e17bdfa3 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 28 Jul 2020 13:50:02 +0200 Subject: [PATCH 29/75] [Uptime] Convert kuery bar to ts (#70310) Co-authored-by: Elastic Machine --- .../__tests__/alert_monitor_status.test.tsx | 10 - .../overview/alerts/alert_monitor_status.tsx | 3 - .../alert_monitor_status.tsx | 4 - .../overview/kuery_bar/kuery_bar.tsx | 22 +- .../kuery_bar/typeahead/click_outside.js | 40 --- .../overview/kuery_bar/typeahead/index.d.ts | 46 --- .../overview/kuery_bar/typeahead/index.js | 245 -------------- .../overview/kuery_bar/typeahead/index.ts | 7 + .../kuery_bar/typeahead/suggestion.js | 140 -------- .../kuery_bar/typeahead/suggestion.tsx | 89 +++++ .../kuery_bar/typeahead/suggestions.js | 111 ------ .../kuery_bar/typeahead/suggestions.tsx | 116 +++++++ .../overview/kuery_bar/typeahead/typehead.tsx | 318 ++++++++++++++++++ .../plugins/uptime/public/pages/overview.tsx | 8 - 14 files changed, 543 insertions(+), 616 deletions(-) delete mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/click_outside.js delete mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.d.ts delete mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.js create mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.ts delete mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.js create mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.tsx delete mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.js create mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.tsx create mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/typehead.tsx diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx index f3f3d583fd938..f26da59238b20 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx @@ -17,10 +17,6 @@ describe('alert monitor status component', () => { timerangeUnit: 'h', timerangeCount: 21, }, - autocomplete: { - addQuerySuggestionProvider: jest.fn(), - getQuerySuggestions: jest.fn(), - }, enabled: true, hasFilters: false, isOldAlert: true, @@ -45,12 +41,6 @@ describe('alert monitor status component', () => { /> = (p setAlertParams('search', value)} diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx index 4ac0355f5edc8..50b6fe2aa0ef1 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx @@ -7,7 +7,6 @@ import React, { useMemo, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; -import { DataPublicPluginSetup } from 'src/plugins/data/public'; import { isRight } from 'fp-ts/lib/Either'; import { selectMonitorStatusAlert, @@ -32,7 +31,6 @@ import { useUpdateKueryString } from '../../../../hooks'; interface Props { alertParams: { [key: string]: any }; - autocomplete: DataPublicPluginSetup['autocomplete']; enabled: boolean; numTimes: number; setAlertParams: (key: string, value: any) => void; @@ -43,7 +41,6 @@ interface Props { } export const AlertMonitorStatus: React.FC = ({ - autocomplete, enabled, numTimes, setAlertParams, @@ -122,7 +119,6 @@ export const AlertMonitorStatus: React.FC = ({ return ( ({ suggestions: [], isLoadingIndexPattern: true, @@ -80,7 +85,7 @@ export function KueryBar({ const indexPatternMissing = loading && !indexPattern; - async function onChange(inputValue: string, selectionStart: number) { + async function onChange(inputValue: string, selectionStart: number | null) { if (!indexPattern) { return; } @@ -94,7 +99,7 @@ export function KueryBar({ try { const suggestions = ( - (await autocompleteService.getQuerySuggestions({ + (await autocomplete.getQuerySuggestions({ language: 'kuery', indexPatterns: [indexPattern], query: inputValue, @@ -111,8 +116,7 @@ export function KueryBar({ }, ], })) || [] - ).filter((suggestion) => !startsWith(suggestion.text, 'span.')); - + ).filter((suggestion: QuerySuggestion) => !startsWith(suggestion.text, 'span.')); if (currentRequest !== currentRequestCheck) { return; } @@ -155,8 +159,8 @@ export function KueryBar({ return ( { - this.nodeRef = node; - }; - - onClick = (event) => { - if (this.nodeRef && !this.nodeRef.contains(event.target)) { - this.props.onClickOutside(); - } - }; - - render() { - return ( -
      - {this.props.children} -
      - ); - } -} - -ClickOutside.propTypes = { - onClickOutside: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.d.ts b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.d.ts deleted file mode 100644 index 751170f3b1cf7..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; - -interface TypeaheadProps { - onChange: (inputValue: string, selectionStart: number) => void; - onSubmit: (inputValue: string) => void; - loadMore: () => void; - suggestions: unknown[]; - queryExample: string; - initialValue?: string; - isLoading?: boolean; - disabled?: boolean; -} - -export class Typeahead extends React.Component { - incrementIndex(currentIndex: any): void; - - decrementIndex(currentIndex: any): void; - - onKeyUp(event: any): void; - - onKeyDown(event: any): void; - - selectSuggestion(suggestion: any): void; - - onClickOutside(): void; - - onChangeInputValue(event: any): void; - - onClickInput(event: any): void; - - onClickSuggestion(suggestion: any): void; - - onMouseEnterSuggestion(index: any): void; - - onSubmit(): void; - - render(): any; - - loadMore(): void; -} diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.js b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.js deleted file mode 100644 index 17141235d8bf2..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.js +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import Suggestions from './suggestions'; -import ClickOutside from './click_outside'; -import { EuiFieldSearch, EuiProgress } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -const KEY_CODES = { - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - ENTER: 13, - ESC: 27, - TAB: 9, -}; - -export class Typeahead extends Component { - state = { - isSuggestionsVisible: false, - index: null, - value: '', - inputIsPristine: true, - lastSubmitted: '', - selected: null, - }; - - static getDerivedStateFromProps(props, state) { - if (state.inputIsPristine && props.initialValue) { - return { - value: props.initialValue, - }; - } - - return null; - } - - incrementIndex = (currentIndex) => { - let nextIndex = currentIndex + 1; - if (currentIndex === null || nextIndex >= this.props.suggestions.length) { - nextIndex = 0; - } - this.setState({ index: nextIndex }); - }; - - decrementIndex = (currentIndex) => { - let previousIndex = currentIndex - 1; - if (previousIndex < 0) { - previousIndex = null; - } - this.setState({ index: previousIndex }); - }; - - onKeyUp = (event) => { - const { selectionStart } = event.target; - const { value } = this.state; - switch (event.keyCode) { - case KEY_CODES.LEFT: - this.setState({ isSuggestionsVisible: true }); - this.props.onChange(value, selectionStart); - break; - case KEY_CODES.RIGHT: - this.setState({ isSuggestionsVisible: true }); - this.props.onChange(value, selectionStart); - break; - } - }; - - onKeyDown = (event) => { - const { isSuggestionsVisible, index, value } = this.state; - switch (event.keyCode) { - case KEY_CODES.DOWN: - event.preventDefault(); - if (isSuggestionsVisible) { - this.incrementIndex(index); - } else { - this.setState({ isSuggestionsVisible: true, index: 0 }); - } - break; - case KEY_CODES.UP: - event.preventDefault(); - if (isSuggestionsVisible) { - this.decrementIndex(index); - } - break; - case KEY_CODES.ENTER: - event.preventDefault(); - if (isSuggestionsVisible && this.props.suggestions[index]) { - this.selectSuggestion(this.props.suggestions[index]); - } else { - this.setState({ isSuggestionsVisible: false }); - this.props.onSubmit(value); - } - break; - case KEY_CODES.ESC: - event.preventDefault(); - this.setState({ isSuggestionsVisible: false }); - break; - case KEY_CODES.TAB: - this.setState({ isSuggestionsVisible: false }); - break; - } - }; - - selectSuggestion = (suggestion) => { - const nextInputValue = - this.state.value.substr(0, suggestion.start) + - suggestion.text + - this.state.value.substr(suggestion.end); - - this.setState({ value: nextInputValue, index: null, selected: suggestion }); - this.props.onChange(nextInputValue, nextInputValue.length); - }; - - onClickOutside = () => { - if (this.state.isSuggestionsVisible) { - this.setState({ isSuggestionsVisible: false }); - this.onSubmit(); - } - }; - - onChangeInputValue = (event) => { - const { value, selectionStart } = event.target; - const hasValue = Boolean(value.trim()); - this.setState({ - value, - inputIsPristine: false, - isSuggestionsVisible: hasValue, - index: null, - }); - - if (!hasValue) { - this.props.onSubmit(value); - } - this.props.onChange(value, selectionStart); - }; - - onClickInput = (event) => { - const { selectionStart } = event.target; - this.props.onChange(this.state.value, selectionStart); - }; - - onClickSuggestion = (suggestion) => { - this.selectSuggestion(suggestion); - this.inputRef.focus(); - }; - - onMouseEnterSuggestion = (index) => { - this.setState({ index }); - }; - - onSubmit = () => { - const { value, lastSubmitted, selected } = this.state; - - if ( - lastSubmitted !== value && - selected && - (selected.type === 'value' || selected.text.trim() === ': *') - ) { - this.props.onSubmit(value); - this.setState({ lastSubmitted: value, selected: null }); - } - }; - - onFocus = () => { - this.setState({ isSuggestionsVisible: true }); - }; - - render() { - return ( - -
      - { - if (node) { - this.inputRef = node; - } - }} - disabled={this.props.disabled} - value={this.state.value} - onKeyDown={this.onKeyDown} - onKeyUp={this.onKeyUp} - onFocus={this.onFocus} - onChange={this.onChangeInputValue} - onClick={this.onClickInput} - autoComplete="off" - spellCheck={false} - /> - - {this.props.isLoading && ( - - )} -
      - - -
      - ); - } -} - -Typeahead.propTypes = { - initialValue: PropTypes.string, - isLoading: PropTypes.bool, - disabled: PropTypes.bool, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - loadMore: PropTypes.func.isRequired, - suggestions: PropTypes.array.isRequired, - queryExample: PropTypes.string.isRequired, -}; - -Typeahead.defaultProps = { - isLoading: false, - disabled: false, - suggestions: [], -}; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.ts b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.ts new file mode 100644 index 0000000000000..6bf1226131e29 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { Typeahead } from './typehead'; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.js b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.js deleted file mode 100644 index 615a444d23e73..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.js +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import { EuiIcon } from '@elastic/eui'; -import { - fontFamilyCode, - px, - units, - fontSizes, - unit, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../../apm/public/style/variables'; -import { tint } from 'polished'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; - -function getIconColor(type) { - switch (type) { - case 'field': - return theme.euiColorVis7; - case 'value': - return theme.euiColorVis0; - case 'operator': - return theme.euiColorVis1; - case 'conjunction': - return theme.euiColorVis3; - case 'recentSearch': - return theme.euiColorMediumShade; - } -} - -const Description = styled.div` - color: ${theme.euiColorDarkShade}; - - p { - display: inline; - - span { - font-family: ${fontFamilyCode}; - color: ${theme.euiColorFullShade}; - padding: 0 ${px(units.quarter)}; - display: inline-block; - } - } -`; - -const ListItem = styled.button` - width: inherit; - font-size: ${fontSizes.small}; - height: ${px(units.double)}; - align-items: center; - display: flex; - background: ${(props) => (props.selected ? theme.euiColorLightestShade : 'initial')}; - cursor: pointer; - border-radius: ${px(units.quarter)}; - - ${Description} { - p span { - background: ${(props) => - props.selected ? theme.euiColorEmptyShade : theme.euiColorLightestShade}; - } - @media only screen and (max-width: ${theme.euiBreakpoints.s}) { - margin-left: auto; - text-align: end; - } - } -`; - -const Icon = styled.div` - flex: 0 0 ${px(units.double)}; - background: ${(props) => tint(0.1, getIconColor(props.type))}; - color: ${(props) => getIconColor(props.type)}; - width: 100%; - height: 100%; - text-align: center; - line-height: ${px(units.double)}; -`; - -const TextValue = styled.div` - text-align: left; - flex: 0 0 ${px(unit * 12)}; - color: ${theme.euiColorDarkestShade}; - padding: 0 ${px(units.half)}; - - @media only screen and (max-width: ${theme.euiBreakpoints.s}) { - flex: 0 0 ${px(unit * 8)}; - } - @media only screen and (min-width: 1300px) { - flex: 0 0 ${px(unit * 16)}; - } -`; - -function getEuiIconType(type) { - switch (type) { - case 'field': - return 'kqlField'; - case 'value': - return 'kqlValue'; - case 'recentSearch': - return 'search'; - case 'conjunction': - return 'kqlSelector'; - case 'operator': - return 'kqlOperand'; - default: - throw new Error('Unknown type', type); - } -} - -function Suggestion(props) { - return ( - props.onClick(props.suggestion)} - onMouseEnter={props.onMouseEnter} - > - - - - {props.suggestion.text} - {props.suggestion.description} - - ); -} - -Suggestion.propTypes = { - onClick: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func.isRequired, - selected: PropTypes.bool, - suggestion: PropTypes.object.isRequired, - innerRef: PropTypes.func.isRequired, -}; - -export default Suggestion; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.tsx new file mode 100644 index 0000000000000..1dc89d2795309 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useRef, useEffect, RefObject } from 'react'; +import styled from 'styled-components'; +import { EuiSuggestItem } from '@elastic/eui'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; + +import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public'; + +const SuggestionItem = styled.div<{ selected: boolean }>` + background: ${(props) => (props.selected ? theme.euiColorLightestShade : 'initial')}; +`; + +function getIconColor(type: string) { + switch (type) { + case 'field': + return 'tint5'; + case 'value': + return 'tint0'; + case 'operator': + return 'tint1'; + case 'conjunction': + return 'tint3'; + case 'recentSearch': + return 'tint10'; + default: + return 'tint5'; + } +} + +function getEuiIconType(type: string) { + switch (type) { + case 'field': + return 'kqlField'; + case 'value': + return 'kqlValue'; + case 'recentSearch': + return 'search'; + case 'conjunction': + return 'kqlSelector'; + case 'operator': + return 'kqlOperand'; + default: + throw new Error(`Unknown type ${type}`); + } +} + +interface SuggestionProps { + onClick: (sug: QuerySuggestion) => void; + onMouseEnter: () => void; + selected: boolean; + suggestion: QuerySuggestion; + innerRef: (node: any) => void; +} + +export const Suggestion: React.FC = ({ + innerRef, + selected, + suggestion, + onClick, + onMouseEnter, +}) => { + const childNode: RefObject = useRef(null); + + useEffect(() => { + if (childNode.current) { + innerRef(childNode.current); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [childNode]); + + return ( + + onClick(suggestion)} + onMouseEnter={onMouseEnter} + // @ts-ignore + description={suggestion.description} + /> + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.js b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.js deleted file mode 100644 index 8d614d7ea1aec..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import { isEmpty } from 'lodash'; -import Suggestion from './suggestion'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { units, px, unit } from '../../../../../../apm/public/style/variables'; -import { tint } from 'polished'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; - -const List = styled.ul` - width: 100%; - border: 1px solid ${theme.euiColorLightShade}; - border-radius: ${px(units.quarter)}; - box-shadow: 0px ${px(units.quarter)} ${px(units.double)} ${tint(0.1, theme.euiColorFullShade)}; - position: absolute; - background: #fff; - z-index: 10; - left: 0; - max-height: ${px(unit * 20)}; - overflow: scroll; -`; - -class Suggestions extends Component { - childNodes = []; - - scrollIntoView = () => { - const parent = this.parentNode; - const child = this.childNodes[this.props.index]; - - if (this.props.index == null || !parent || !child) { - return; - } - - const scrollTop = Math.max( - Math.min(parent.scrollTop, child.offsetTop), - child.offsetTop + child.offsetHeight - parent.offsetHeight - ); - - parent.scrollTop = scrollTop; - }; - - handleScroll = () => { - const parent = this.parentNode; - - if (!this.props.loadMore || !parent) { - return; - } - - const position = parent.scrollTop + parent.offsetHeight; - const height = parent.scrollHeight; - const remaining = height - position; - const margin = 50; - - if (!height || !position) { - return; - } - if (remaining <= margin) { - this.props.loadMore(); - } - }; - - componentDidUpdate(prevProps) { - if (prevProps.index !== this.props.index) { - this.scrollIntoView(); - } - } - - render() { - if (!this.props.show || isEmpty(this.props.suggestions)) { - return null; - } - - const suggestions = this.props.suggestions.map((suggestion, index) => { - const key = suggestion + '_' + index; - return ( - (this.childNodes[index] = node)} - selected={index === this.props.index} - suggestion={suggestion} - onClick={this.props.onClick} - onMouseEnter={() => this.props.onMouseEnter(index)} - key={key} - /> - ); - }); - - return ( - (this.parentNode = node)} onScroll={this.handleScroll}> - {suggestions} - - ); - } -} - -Suggestions.propTypes = { - index: PropTypes.number, - onClick: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func.isRequired, - show: PropTypes.bool, - suggestions: PropTypes.array.isRequired, - loadMore: PropTypes.func.isRequired, -}; - -export default Suggestions; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.tsx new file mode 100644 index 0000000000000..dcd8df1ba18ef --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useRef, useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { isEmpty } from 'lodash'; +import { tint } from 'polished'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { Suggestion } from './suggestion'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { units, px, unit } from '../../../../../../apm/public/style/variables'; +import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public'; + +const List = styled.ul` + width: 100%; + border: 1px solid ${theme.euiColorLightShade}; + border-radius: ${px(units.quarter)}; + box-shadow: 0px ${px(units.quarter)} ${px(units.double)} ${tint(0.1, theme.euiColorFullShade)}; + background: #fff; + z-index: 10; + max-height: ${px(unit * 20)}; + overflow: scroll; + position: absolute; +`; + +interface SuggestionsProps { + index: number; + onClick: (sug: QuerySuggestion) => void; + onMouseEnter: (index: number) => void; + show?: boolean; + suggestions: QuerySuggestion[]; + loadMore: () => void; +} + +export const Suggestions: React.FC = ({ + show, + index, + onClick, + suggestions, + onMouseEnter, + loadMore, +}) => { + const [childNodes, setChildNodes] = useState([]); + + const parentNode = useRef(null); + + useEffect(() => { + const scrollIntoView = () => { + const parent = parentNode.current; + const child = childNodes[index]; + + if (index == null || !parent || !child) { + return; + } + + const scrollTop = Math.max( + Math.min(parent.scrollTop, child.offsetTop), + child.offsetTop + child.offsetHeight - parent.offsetHeight + ); + + parent.scrollTop = scrollTop; + }; + scrollIntoView(); + }, [index, childNodes]); + + if (!show || isEmpty(suggestions)) { + return null; + } + + const handleScroll = () => { + const parent = parentNode.current; + + if (!loadMore || !parent) { + return; + } + + const position = parent.scrollTop + parent.offsetHeight; + const height = parent.scrollHeight; + const remaining = height - position; + const margin = 50; + + if (!height || !position) { + return; + } + if (remaining <= margin) { + loadMore(); + } + }; + + const suggestionsNodes = suggestions.map((suggestion, currIndex) => { + const key = suggestion + '_' + currIndex; + return ( + { + const nodes = childNodes; + nodes[currIndex] = node; + setChildNodes([...nodes]); + }} + selected={currIndex === index} + suggestion={suggestion} + onClick={onClick} + onMouseEnter={() => onMouseEnter(currIndex)} + key={key} + /> + ); + }); + + return ( + + {suggestionsNodes} + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/typehead.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/typehead.tsx new file mode 100644 index 0000000000000..5582818b6f09b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/typehead.tsx @@ -0,0 +1,318 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { KeyboardEvent, ChangeEvent, MouseEvent, useState, useRef, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFieldSearch, EuiProgress, EuiOutsideClickDetector } from '@elastic/eui'; +import { Suggestions } from './suggestions'; +import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public'; + +const KEY_CODES = { + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + ENTER: 13, + ESC: 27, + TAB: 9, +}; + +interface TypeaheadState { + isSuggestionsVisible: boolean; + index: number | null; + value: string; + inputIsPristine: boolean; + lastSubmitted: string; + selected: QuerySuggestion | null; +} + +interface TypeaheadProps { + onChange: (inputValue: string, selectionStart: number | null) => void; + onSubmit: (inputValue: string) => void; + suggestions: QuerySuggestion[]; + queryExample: string; + initialValue?: string; + isLoading?: boolean; + disabled?: boolean; + dataTestSubj: string; + ariaLabel: string; + loadMore: () => void; +} + +export const Typeahead: React.FC = ({ + initialValue, + suggestions, + onChange, + onSubmit, + dataTestSubj, + ariaLabel, + disabled, + isLoading, + loadMore, +}) => { + const [state, setState] = useState({ + isSuggestionsVisible: false, + index: null, + value: '', + inputIsPristine: true, + lastSubmitted: '', + selected: null, + }); + + const inputRef = useRef(); + + useEffect(() => { + if (state.inputIsPristine && initialValue) { + setState((prevState) => ({ + ...prevState, + value: initialValue, + })); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialValue]); + + const incrementIndex = (currentIndex: number) => { + let nextIndex = currentIndex + 1; + if (currentIndex === null || nextIndex >= suggestions.length) { + nextIndex = 0; + } + + setState((prevState) => ({ + ...prevState, + index: nextIndex, + })); + }; + + const decrementIndex = (currentIndex: number) => { + let previousIndex: number | null = currentIndex - 1; + if (previousIndex < 0) { + previousIndex = null; + } + + setState((prevState) => ({ + ...prevState, + index: previousIndex, + })); + }; + + const onKeyUp = (event: KeyboardEvent & ChangeEvent) => { + const { selectionStart } = event.target; + const { value } = state; + switch (event.keyCode) { + case KEY_CODES.LEFT: + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: true, + })); + onChange(value, selectionStart); + break; + case KEY_CODES.RIGHT: + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: true, + })); + onChange(value, selectionStart); + break; + } + }; + + const onKeyDown = (event: KeyboardEvent) => { + const { isSuggestionsVisible, index, value } = state; + switch (event.keyCode) { + case KEY_CODES.DOWN: + event.preventDefault(); + if (isSuggestionsVisible) { + incrementIndex(index!); + } else { + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: true, + index: 0, + })); + } + break; + case KEY_CODES.UP: + event.preventDefault(); + if (isSuggestionsVisible) { + decrementIndex(index!); + } + break; + case KEY_CODES.ENTER: + event.preventDefault(); + if (isSuggestionsVisible && suggestions[index!]) { + selectSuggestion(suggestions[index!]); + } else { + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: false, + })); + + onSubmit(value); + } + break; + case KEY_CODES.ESC: + event.preventDefault(); + + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: false, + })); + + break; + case KEY_CODES.TAB: + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: false, + })); + break; + } + }; + + const selectSuggestion = (suggestion: QuerySuggestion) => { + const nextInputValue = + state.value.substr(0, suggestion.start) + + suggestion.text + + state.value.substr(suggestion.end); + + setState((prevState) => ({ + ...prevState, + value: nextInputValue, + index: null, + selected: suggestion, + })); + + onChange(nextInputValue, nextInputValue.length); + }; + + const onClickOutside = () => { + if (state.isSuggestionsVisible) { + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: false, + })); + + onSuggestionSubmit(); + } + }; + + const onChangeInputValue = (event: ChangeEvent) => { + const { value, selectionStart } = event.target; + const hasValue = Boolean(value.trim()); + + setState((prevState) => ({ + ...prevState, + value, + inputIsPristine: false, + isSuggestionsVisible: hasValue, + index: null, + })); + + if (!hasValue) { + onSubmit(value); + } + onChange(value, selectionStart!); + }; + + const onClickInput = (event: MouseEvent & ChangeEvent) => { + event.stopPropagation(); + const { selectionStart } = event.target; + onChange(state.value, selectionStart!); + }; + + const onFocus = () => { + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: true, + })); + }; + + const onClickSuggestion = (suggestion: QuerySuggestion) => { + selectSuggestion(suggestion); + if (inputRef.current) inputRef.current.focus(); + }; + + const onMouseEnterSuggestion = (index: number) => { + setState({ ...state, index }); + + setState((prevState) => ({ + ...prevState, + index, + })); + }; + + const onSuggestionSubmit = () => { + const { value, lastSubmitted, selected } = state; + + if ( + lastSubmitted !== value && + selected && + (selected.type === 'value' || selected.text.trim() === ': *') + ) { + onSubmit(value); + + setState((prevState) => ({ + ...prevState, + lastSubmitted: value, + selected: null, + })); + } + }; + + return ( + + +
      + { + if (node) { + inputRef.current = node; + } + }} + disabled={disabled} + value={state.value} + onKeyDown={onKeyDown} + onKeyUp={onKeyUp} + onFocus={onFocus} + onChange={onChangeInputValue} + onClick={onClickInput} + autoComplete="off" + spellCheck={false} + /> + + {isLoading && ( + + )} +
      + + +
      +
      + ); +}; diff --git a/x-pack/plugins/uptime/public/pages/overview.tsx b/x-pack/plugins/uptime/public/pages/overview.tsx index 32c86435913f7..3b58ea1e5cf84 100644 --- a/x-pack/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/plugins/uptime/public/pages/overview.tsx @@ -18,7 +18,6 @@ import { useTrackPageview } from '../../../observability/public'; import { MonitorList } from '../components/overview/monitor_list/monitor_list_container'; import { EmptyState, FilterGroup, KueryBar, ParsingErrorCallout } from '../components/overview'; import { StatusPanel } from '../components/overview/status_panel'; -import { useKibana } from '../../../../../src/plugins/kibana_react/public'; interface Props { loading: boolean; @@ -43,12 +42,6 @@ export const OverviewPageComponent = React.memo( const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams(); const { search, filters: urlFilters } = params; - const { - services: { - data: { autocomplete }, - }, - } = useKibana(); - useTrackPageview({ app: 'uptime', path: 'overview' }); useTrackPageview({ app: 'uptime', path: 'overview', delay: 15000 }); @@ -77,7 +70,6 @@ export const OverviewPageComponent = React.memo( aria-label={i18n.translate('xpack.uptime.filterBar.ariaLabel', { defaultMessage: 'Input filter criteria for the overview page', })} - autocomplete={autocomplete} data-test-subj="xpack.uptime.filterBar" />
      From 7a10077776a729a1f7dc674c04e73b757a1dd2f4 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 28 Jul 2020 12:53:36 +0100 Subject: [PATCH 30/75] [Security Solution] Template unit tests (#72399) * add unit test for failure cases * add unit tests * update wording * fix error when update template without ttid or ttversion * fix unit test * add comment * review Co-authored-by: Elastic Machine --- .../rules/pre_packaged_rules/translations.ts | 2 +- .../update_callout.test.tsx | 92 +++ .../pre_packaged_rules/update_callout.tsx | 9 +- .../rules/use_pre_packaged_rules.tsx | 4 +- .../detection_engine/rules/helpers.test.tsx | 136 +++++ ...get_prepackaged_rules_status_route.test.ts | 51 ++ .../routes/import_timelines_route.test.ts | 22 + .../routes/utils/compare_timelines_status.ts | 40 +- .../routes/utils/failure_cases.test.ts | 542 ++++++++++++++++++ .../timeline/routes/utils/failure_cases.ts | 17 +- 10 files changed, 888 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts index 37c1715c05d71..49da7dbf6d514 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts @@ -24,7 +24,7 @@ export const PRE_BUILT_MSG = i18n.translate( export const PRE_BUILT_ACTION = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.prePackagedRules.loadPreBuiltButton', { - defaultMessage: 'Load prebuilt detection rules', + defaultMessage: 'Load prebuilt detection rules and timeline templates', } ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx index 5033fcd11dc7c..283bba462792c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx @@ -9,6 +9,7 @@ import { shallow } from 'enzyme'; import { UpdatePrePackagedRulesCallOut } from './update_callout'; import { useKibana } from '../../../../common/lib/kibana'; + jest.mock('../../../../common/lib/kibana'); describe('UpdatePrePackagedRulesCallOut', () => { @@ -22,6 +23,7 @@ describe('UpdatePrePackagedRulesCallOut', () => { }, }); }); + it('renders correctly', () => { const wrapper = shallow( { expect(wrapper.find('EuiCallOut')).toHaveLength(1); }); + + it('renders callOutMessage correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines = 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout"]').find('p').text()).toEqual( + 'You can update 1 Elastic prebuilt ruleRelease notes' + ); + }); + + it('renders buttonTitle correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines = 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout-button"]').prop('children')).toEqual( + 'Update 1 Elastic prebuilt rule' + ); + }); + + it('renders callOutMessage correctly: numberOfUpdatedRules = 0 and numberOfUpdatedTimelines > 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout"]').find('p').text()).toEqual( + 'You can update 1 Elastic prebuilt timelineRelease notes' + ); + }); + + it('renders buttonTitle correctly: numberOfUpdatedRules = 0 and numberOfUpdatedTimelines > 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout-button"]').prop('children')).toEqual( + 'Update 1 Elastic prebuilt timeline' + ); + }); + + it('renders callOutMessage correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines > 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout"]').find('p').text()).toEqual( + 'You can update 1 Elastic prebuilt rule and 1 Elastic prebuilt timeline. Note that this will reload deleted Elastic prebuilt rules.Release notes' + ); + }); + + it('renders buttonTitle correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines > 0', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="update-callout-button"]').prop('children')).toEqual( + 'Update 1 Elastic prebuilt rule and 1 Elastic prebuilt timeline' + ); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx index 4b454a9ed4d4a..30f8cfa7fb3a5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx @@ -51,7 +51,7 @@ const UpdatePrePackagedRulesCallOutComponent: React.FC +

      {prepackagedRulesOrTimelines?.callOutMessage}
      @@ -62,7 +62,12 @@ const UpdatePrePackagedRulesCallOutComponent: React.FC

      - + {prepackagedRulesOrTimelines?.buttonTitle}
      diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx index 08c85695e9313..d82d97883a3d0 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -169,7 +169,9 @@ export const usePrePackagedRules = ({ if ( isSubscribed && ((prePackagedRuleStatusResponse.rules_not_installed === 0 && - prePackagedRuleStatusResponse.rules_not_updated === 0) || + prePackagedRuleStatusResponse.rules_not_updated === 0 && + prePackagedRuleStatusResponse.timelines_not_installed === 0 && + prePackagedRuleStatusResponse.timelines_not_updated === 0) || iterationTryOfFetchingPrePackagedCount > 100) ) { setLoadingCreatePrePackagedRules(false); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx index c01317e4f48c5..b40243efcfb46 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx @@ -13,6 +13,8 @@ import { getActionsStepsData, getHumanizedDuration, getModifiedAboutDetailsData, + getPrePackagedRuleStatus, + getPrePackagedTimelineStatus, determineDetailsValue, userHasNoPermissions, } from './helpers'; @@ -394,4 +396,138 @@ describe('rule helpers', () => { expect(result).toEqual(userHasNoPermissionsExpectedResult); }); }); + + describe('getPrePackagedRuleStatus', () => { + test('ruleNotInstalled', () => { + const rulesInstalled = 0; + const rulesNotInstalled = 1; + const rulesNotUpdated = 0; + const result: string = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + expect(result).toEqual('ruleNotInstalled'); + }); + + test('ruleInstalled', () => { + const rulesInstalled = 1; + const rulesNotInstalled = 0; + const rulesNotUpdated = 0; + const result: string = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + expect(result).toEqual('ruleInstalled'); + }); + + test('someRuleUninstall', () => { + const rulesInstalled = 1; + const rulesNotInstalled = 1; + const rulesNotUpdated = 0; + const result: string = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + expect(result).toEqual('someRuleUninstall'); + }); + + test('ruleNeedUpdate', () => { + const rulesInstalled = 1; + const rulesNotInstalled = 0; + const rulesNotUpdated = 1; + const result: string = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + expect(result).toEqual('ruleNeedUpdate'); + }); + + test('unknown', () => { + const rulesInstalled = null; + const rulesNotInstalled = null; + const rulesNotUpdated = null; + const result: string = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + expect(result).toEqual('unknown'); + }); + }); + + describe('getPrePackagedTimelineStatus', () => { + test('timelinesNotInstalled', () => { + const timelinesInstalled = 0; + const timelinesNotInstalled = 1; + const timelinesNotUpdated = 0; + const result: string = getPrePackagedTimelineStatus( + timelinesInstalled, + timelinesNotInstalled, + timelinesNotUpdated + ); + + expect(result).toEqual('timelinesNotInstalled'); + }); + + test('timelinesInstalled', () => { + const timelinesInstalled = 1; + const timelinesNotInstalled = 0; + const timelinesNotUpdated = 0; + const result: string = getPrePackagedTimelineStatus( + timelinesInstalled, + timelinesNotInstalled, + timelinesNotUpdated + ); + + expect(result).toEqual('timelinesInstalled'); + }); + + test('someTimelineUninstall', () => { + const timelinesInstalled = 1; + const timelinesNotInstalled = 1; + const timelinesNotUpdated = 0; + const result: string = getPrePackagedTimelineStatus( + timelinesInstalled, + timelinesNotInstalled, + timelinesNotUpdated + ); + + expect(result).toEqual('someTimelineUninstall'); + }); + + test('timelineNeedUpdate', () => { + const timelinesInstalled = 1; + const timelinesNotInstalled = 0; + const timelinesNotUpdated = 1; + const result: string = getPrePackagedTimelineStatus( + timelinesInstalled, + timelinesNotInstalled, + timelinesNotUpdated + ); + + expect(result).toEqual('timelineNeedUpdate'); + }); + + test('unknown', () => { + const timelinesInstalled = null; + const timelinesNotInstalled = null; + const timelinesNotUpdated = null; + const result: string = getPrePackagedTimelineStatus( + timelinesInstalled, + timelinesNotInstalled, + timelinesNotUpdated + ); + + expect(result).toEqual('unknown'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts index f8b6f7e3ddcba..fa2a575d3f69f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts @@ -14,6 +14,11 @@ import { } from '../__mocks__/request_responses'; import { requestContextMock, serverMock, createMockConfig } from '../__mocks__'; import { SecurityPluginSetup } from '../../../../../../security/server'; +import { checkTimelinesStatus } from '../../../timeline/routes/utils/check_timelines_status'; +import { + mockCheckTimelinesStatusBeforeInstallResult, + mockCheckTimelinesStatusAfterInstallResult, +} from '../../../timeline/routes/__mocks__/import_timelines'; jest.mock('../../rules/get_prepackaged_rules', () => { return { @@ -38,6 +43,12 @@ jest.mock('../../rules/get_prepackaged_rules', () => { }; }); +jest.mock('../../../timeline/routes/utils/check_timelines_status', () => { + return { + checkTimelinesStatus: jest.fn(), + }; +}); + describe('get_prepackaged_rule_status_route', () => { const mockGetCurrentUser = { user: { @@ -126,5 +137,45 @@ describe('get_prepackaged_rule_status_route', () => { timelines_not_updated: 0, }); }); + + test('0 timelines installed, 3 timelines not installed, 0 timelines not updated', async () => { + clients.alertsClient.find.mockResolvedValue(getEmptyFindResult()); + (checkTimelinesStatus as jest.Mock).mockResolvedValue( + mockCheckTimelinesStatusBeforeInstallResult + ); + const request = getPrepackagedRulesStatusRequest(); + const response = await server.inject(request, context); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 1, + rules_not_updated: 0, + timelines_installed: 0, + timelines_not_installed: 3, + timelines_not_updated: 0, + }); + }); + + test('3 timelines installed, 0 timelines not installed, 0 timelines not updated', async () => { + clients.alertsClient.find.mockResolvedValue(getEmptyFindResult()); + (checkTimelinesStatus as jest.Mock).mockResolvedValue( + mockCheckTimelinesStatusAfterInstallResult + ); + const request = getPrepackagedRulesStatusRequest(); + const response = await server.inject(request, context); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 1, + rules_not_updated: 0, + timelines_installed: 3, + timelines_not_installed: 0, + timelines_not_updated: 0, + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts index fe5993cb0161d..b817896e901c1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts @@ -598,6 +598,28 @@ describe('import timeline templates', () => { mockNewTemplateTimelineId ); }); + + test('should return 200 if create via import without a templateTimelineId or templateTimelineVersion', async () => { + mockGetTupleDuplicateErrorsAndUniqueTimeline.mockReturnValue([ + mockDuplicateIdErrors, + [ + { + ...mockUniqueParsedTemplateTimelineObjects[0], + templateTimelineId: null, + templateTimelineVersion: null, + }, + ], + ]); + const mockRequest = getImportTimelinesRequest(); + const result = await server.inject(mockRequest, context); + expect(result.body).toEqual({ + errors: [], + success: true, + success_count: 1, + timelines_installed: 1, + timelines_updated: 0, + }); + }); }); describe('Import a timeline template already exist', () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts index d61d217a4cf49..f9515741d1250 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { isEmpty } from 'lodash/fp'; +import { isEmpty, isInteger } from 'lodash/fp'; import { TimelineTypeLiteralWithNull, TimelineType, @@ -71,13 +71,28 @@ export class CompareTimelinesStatus { } public get isCreatable() { + const noExistingTimeline = this.timelineObject.isCreatable && !this.isHandlingTemplateTimeline; + + const templateCreatable = + this.isHandlingTemplateTimeline && this.templateTimelineObject.isCreatable; + + const noExistingTimelineOrTemplate = templateCreatable && this.timelineObject.isCreatable; + + // From Line 87-91 is the condition for creating a template via import without given a templateTimelineId or templateTimelineVersion, + // but keep the existing savedObjectId and version there. + // Therefore even the timeline exists, we still allow it to create a new timeline template by assigning a templateTimelineId and templateTimelineVersion. + // https://github.com/elastic/kibana/pull/67496#discussion_r454337222 + // Line 90-91 means that we want to make sure the existing timeline retrieved by savedObjectId is atemplate. + // If it is not a template, we show an error this timeline is already exist instead. + const retriveTemplateViaSavedObjectId = + templateCreatable && + !this.timelineObject.isCreatable && + this.timelineObject.getData?.timelineType === this.timelineType; + return ( this.isTitleValid && !this.isSavedObjectVersionConflict && - ((this.timelineObject.isCreatable && !this.isHandlingTemplateTimeline) || - (this.templateTimelineObject.isCreatable && - this.timelineObject.isCreatable && - this.isHandlingTemplateTimeline)) + (noExistingTimeline || noExistingTimelineOrTemplate || retriveTemplateViaSavedObjectId) ); } @@ -195,24 +210,27 @@ export class CompareTimelinesStatus { } private get isTemplateVersionConflict() { - const version = this.templateTimelineObject?.getVersion; + const templateTimelineVersion = this.templateTimelineObject?.getVersion; const existingTemplateTimelineVersion = this.templateTimelineObject?.data ?.templateTimelineVersion; if ( - version != null && + templateTimelineVersion != null && this.templateTimelineObject.isExists && existingTemplateTimelineVersion != null ) { - return version <= existingTemplateTimelineVersion; - } else if (this.templateTimelineObject.isExists && version == null) { + return templateTimelineVersion <= existingTemplateTimelineVersion; + } else if (this.templateTimelineObject.isExists && templateTimelineVersion == null) { return true; } return false; } private get isTemplateVersionValid() { - const version = this.templateTimelineObject?.getVersion; - return typeof version === 'number' && !this.isTemplateVersionConflict; + const templateTimelineVersion = this.templateTimelineObject?.getVersion; + return ( + templateTimelineVersion == null || + (isInteger(templateTimelineVersion) && !this.isTemplateVersionConflict) + ); } private get isUpdatedTimelineStatusValid() { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts new file mode 100644 index 0000000000000..3c3ad1cf2d7f8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts @@ -0,0 +1,542 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + commonFailureChecker, + checkIsCreateFailureCases, + checkIsUpdateFailureCases, + checkIsCreateViaImportFailureCases, + EMPTY_TITLE_ERROR_MESSAGE, + UPDATE_STATUS_ERROR_MESSAGE, + CREATE_TIMELINE_ERROR_MESSAGE, + CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, + CREATE_TEMPLATE_TIMELINE_WITHOUT_VERSION_ERROR_MESSAGE, + NO_MATCH_ID_ERROR_MESSAGE, + NO_MATCH_VERSION_ERROR_MESSAGE, + NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, + UPDATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, + CREATE_WITH_INVALID_STATUS_ERROR_MESSAGE, + getImportExistingTimelineError, + checkIsUpdateViaImportFailureCases, + NOT_ALLOW_UPDATE_STATUS_ERROR_MESSAGE, + TEMPLATE_TIMELINE_VERSION_CONFLICT_MESSAGE, +} from './failure_cases'; +import { + TimelineStatus, + TimelineType, + TimelineSavedObject, +} from '../../../../../common/types/timeline'; +import { mockGetTimelineValue, mockGetTemplateTimelineValue } from '../__mocks__/import_timelines'; + +describe('failure cases', () => { + describe('commonFailureChecker', () => { + test('If timeline type is draft, it should not return error if title is not given', () => { + const result = commonFailureChecker(TimelineStatus.draft, null); + + expect(result).toBeNull(); + }); + + test('If timeline type is active, it should return error if title is not given', () => { + const result = commonFailureChecker(TimelineStatus.active, null); + + expect(result).toEqual({ + body: EMPTY_TITLE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('If timeline type is immutable, it should return error if title is not given', () => { + const result = commonFailureChecker(TimelineStatus.immutable, null); + + expect(result).toEqual({ + body: EMPTY_TITLE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('If timeline type is not a draft, it should return no error if title is given', () => { + const result = commonFailureChecker(TimelineStatus.active, 'title'); + + expect(result).toBeNull(); + }); + }); + + describe('checkIsCreateFailureCases', () => { + test('Should return error if trying to create a timeline that is already exist', () => { + const isHandlingTemplateTimeline = false; + const version = null; + const templateTimelineVersion = null; + const templateTimelineId = null; + const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTemplateTimeline = null; + const result = checkIsCreateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: CREATE_TIMELINE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('Should return error if trying to create a timeline template that is already exist', () => { + const isHandlingTemplateTimeline = true; + const version = null; + const templateTimelineVersion = 1; + const templateTimelineId = 'template-timeline-id-one'; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsCreateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('Should return error if trying to create a timeline template without providing templateTimelineVersion', () => { + const isHandlingTemplateTimeline = true; + const version = null; + const templateTimelineVersion = null; + const templateTimelineId = 'template-timeline-id-one'; + const existTimeline = null; + const existTemplateTimeline = null; + const result = checkIsCreateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: CREATE_TEMPLATE_TIMELINE_WITHOUT_VERSION_ERROR_MESSAGE, + statusCode: 403, + }); + }); + }); + + describe('checkIsUpdateFailureCases', () => { + test('Should return error if trying to update status field of an existing immutable timeline', () => { + const isHandlingTemplateTimeline = false; + const version = mockGetTimelineValue.version; + const templateTimelineVersion = null; + const templateTimelineId = null; + const existTimeline = { + ...(mockGetTimelineValue as TimelineSavedObject), + status: TimelineStatus.immutable, + }; + const existTemplateTimeline = null; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: UPDATE_STATUS_ERROR_MESSAGE, + statusCode: 403, + }); + }); + + test('Should return error if trying to update status field of an existing immutable timeline template', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = { + ...(mockGetTemplateTimelineValue as TimelineSavedObject), + status: TimelineStatus.immutable, + }; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: UPDATE_STATUS_ERROR_MESSAGE, + statusCode: 403, + }); + }); + + test('should return error if trying to update timelineType field of an existing timeline template', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, + statusCode: 403, + }); + }); + + test('should return error if trying to update a timeline template that does not exist', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = null; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: UPDATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('should return error if there is no matched timeline found by given templateTimelineId', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = { + ...(mockGetTemplateTimelineValue as TimelineSavedObject), + savedObjectId: 'someOtherId', + }; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NO_MATCH_ID_ERROR_MESSAGE, + statusCode: 409, + }); + }); + + test('should return error if given version field is defferent from existing version of timelin template', () => { + const isHandlingTemplateTimeline = true; + const version = 'xxx'; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NO_MATCH_VERSION_ERROR_MESSAGE, + statusCode: 409, + }); + }); + }); + + describe('checkIsCreateViaImportFailureCases', () => { + test('should return error if trying to create a draft timeline', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsCreateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.draft, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: CREATE_WITH_INVALID_STATUS_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('should return error if trying to create a timeline template which is already exist', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsCreateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: getImportExistingTimelineError(mockGetTimelineValue.savedObjectId), + statusCode: 405, + }); + }); + + test('should return error if importe a timeline which is already exists', () => { + const isHandlingTemplateTimeline = false; + const version = mockGetTimelineValue.version; + const templateTimelineVersion = null; + const templateTimelineId = null; + const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTemplateTimeline = null; + const result = checkIsCreateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: getImportExistingTimelineError(mockGetTimelineValue.savedObjectId), + statusCode: 405, + }); + }); + }); + + describe('checkIsUpdateViaImportFailureCases', () => { + test('should return error if trying to update a timeline which does not exist', () => { + const isHandlingTemplateTimeline = false; + const version = mockGetTimelineValue.version; + const templateTimelineVersion = null; + const templateTimelineId = null; + const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTemplateTimeline = null; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: getImportExistingTimelineError(mockGetTimelineValue.savedObjectId), + statusCode: 405, + }); + }); + + test('should return error if trying to update timelineType field of an existing timeline template', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, + statusCode: 403, + }); + }); + + test('should return error if trying to update status field of an existing timeline template', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.immutable, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NOT_ALLOW_UPDATE_STATUS_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('should return error if trying to update a timeline template that does not exist', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = null; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.default, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: UPDATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, + statusCode: 405, + }); + }); + + test('should return error if there is no matched timeline found by given templateTimelineId', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = { + ...(mockGetTemplateTimelineValue as TimelineSavedObject), + savedObjectId: 'someOtherId', + }; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NO_MATCH_ID_ERROR_MESSAGE, + statusCode: 409, + }); + }); + + test('should return error if given version field is defferent from existing version of timelin template', () => { + const isHandlingTemplateTimeline = true; + const version = 'xxx'; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: NO_MATCH_VERSION_ERROR_MESSAGE, + statusCode: 409, + }); + }); + + test('should return error if given templateTimelineVersion field is less or equal to existing templateTimelineVersion of timelin template', () => { + const isHandlingTemplateTimeline = true; + const version = mockGetTemplateTimelineValue.version; + const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; + const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; + const existTimeline = null; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const result = checkIsUpdateViaImportFailureCases( + isHandlingTemplateTimeline, + TimelineStatus.active, + TimelineType.template, + version, + templateTimelineVersion, + templateTimelineId, + existTimeline, + existTemplateTimeline + ); + + expect(result).toEqual({ + body: TEMPLATE_TIMELINE_VERSION_CONFLICT_MESSAGE, + statusCode: 409, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts index d41e8fc190983..b926819d66c92 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts @@ -78,7 +78,10 @@ const commonUpdateTemplateTimelineCheck = ( existTemplateTimeline: TimelineSavedObject | null ) => { if (isHandlingTemplateTimeline) { - if (existTimeline != null && timelineType !== existTimeline.timelineType) { + if ( + (existTimeline != null && timelineType !== existTimeline.timelineType) || + (existTemplateTimeline != null && timelineType !== existTemplateTimeline.timelineType) + ) { return { body: NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, statusCode: 403, @@ -106,11 +109,7 @@ const commonUpdateTemplateTimelineCheck = ( }; } - if ( - existTemplateTimeline != null && - existTemplateTimeline.templateTimelineVersion == null && - existTemplateTimeline.version !== version - ) { + if (existTemplateTimeline != null && existTemplateTimeline.version !== version) { // throw error 409 conflict timeline return { body: NO_MATCH_VERSION_ERROR_MESSAGE, @@ -231,12 +230,6 @@ export const checkIsUpdateViaImportFailureCases = ( }; } } else { - if (existTemplateTimeline != null && timelineType !== existTemplateTimeline?.timelineType) { - return { - body: NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, - statusCode: 403, - }; - } const isStatusValid = ((existTemplateTimeline?.status == null || existTemplateTimeline?.status === TimelineStatus.active) && From 8c710aae3a7702ecd16e7dab997ed331103ff165 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Tue, 28 Jul 2020 14:21:24 +0200 Subject: [PATCH 31/75] [ Functional test ] Increase the waiting time for the filter bar request (#73424) --- .../apps/visualize/input_control_vis/chained_controls.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/apps/visualize/input_control_vis/chained_controls.js b/test/functional/apps/visualize/input_control_vis/chained_controls.js index 179ffa5125a9a..89cca7dc7827e 100644 --- a/test/functional/apps/visualize/input_control_vis/chained_controls.js +++ b/test/functional/apps/visualize/input_control_vis/chained_controls.js @@ -34,6 +34,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.visualize.loadSavedVisualization('chained input control', { navigateToVisualize: false, }); + await testSubjects.waitForEnabled('addFilter', 10000); }); it('should disable child control when parent control is not set', async () => { From 49846834ebae9d2e1d0ac67353649f0c13ed9dd8 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Tue, 28 Jul 2020 15:23:05 +0200 Subject: [PATCH 32/75] [SIEM] Unskips and fixes Cypress tests (#73322) * removes not needed configuration * fixes events columnts tests * unksips persisten timeline test * fixes failing test * skips events test since need more time for investigation Co-authored-by: Elastic Machine --- .../cypress/integration/timeline_local_storage.spec.ts | 3 +-- x-pack/plugins/security_solution/cypress/tasks/common.ts | 8 ++++---- .../security_solution/cypress/tasks/hosts/events.ts | 2 +- x-pack/test/security_solution_cypress/config.ts | 2 -- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts index 7c047459c56cc..383ebe2220585 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts @@ -13,8 +13,7 @@ import { TABLE_COLUMN_EVENTS_MESSAGE } from '../screens/hosts/external_events'; import { waitsForEventsToBeLoaded, openEventsViewerFieldsBrowser } from '../tasks/hosts/events'; import { removeColumn, resetFields } from '../tasks/timeline'; -// Failing: See https://github.com/elastic/kibana/issues/72339 -describe.skip('persistent timeline', () => { +describe('persistent timeline', () => { before(() => { loginAndWaitForPage(HOSTS_URL); openEvents(); diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index a385ad78f63b7..e16db54599981 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -23,14 +23,14 @@ export const drag = (subject: JQuery) => { clientY: subjectLocation.top, force: true, }) - .wait(1000) + .wait(3000) .trigger('mousemove', { button: primaryButton, clientX: subjectLocation.left + dndSloppyClickDetectionThreshold, clientY: subjectLocation.top, force: true, }) - .wait(1000); + .wait(3000); }; /** Drags the subject being dragged on the specified drop target, but does not drop it */ @@ -44,9 +44,9 @@ export const dragWithoutDrop = (dropTarget: JQuery) => { export const drop = (dropTarget: JQuery) => { cy.wrap(dropTarget) .trigger('mousemove', { button: primaryButton, force: true }) - .wait(1000) + .wait(3000) .trigger('mouseup', { force: true }) - .wait(1000); + .wait(3000); }; export const reload = (afterReload: () => void) => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts index 57c819d967883..1d2c4aa8d0834 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts @@ -68,7 +68,7 @@ export const dragAndDropColumn = ({ .eq(column) .then((header) => drag(header)); - cy.wait(3000); // wait for DOM updates before moving + cy.wait(5000); // wait for DOM updates before moving cy.get(DRAGGABLE_HEADER) .eq(newPosition) diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index 1ad3a36cc57ae..83290a60a17a6 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -46,8 +46,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { '--csp.strict=false', // define custom kibana server args here `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, - '--xpack.ingestManager.enabled=true', - '--xpack.ingestManager.fleet.enabled=true', ], }, }; From 19532fc43911d887bebc3ecabae57706509e25ff Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Tue, 28 Jul 2020 15:53:23 +0200 Subject: [PATCH 33/75] [APM] Optimize traces overview (#70200) Co-authored-by: Elastic Machine --- .../app/TraceOverview/TraceList.tsx | 12 +- .../app/TransactionOverview/List/index.tsx | 12 +- .../apm/public/hooks/useTransactionList.ts | 40 +- .../aggregate-latency-metrics/index.ts | 6 +- .../apm/scripts/shared/read-kibana-config.ts | 4 +- ...egister_transaction_duration_alert_type.ts | 2 +- .../metrics/fetch_and_transform_metrics.ts | 37 +- .../lib/metrics/transform_metrics_chart.ts | 37 +- .../get_services/get_services_items_stats.ts | 3 +- .../__snapshots__/fetcher.test.ts.snap | 228 - .../__snapshots__/queries.test.ts.snap | 557 ++- .../__snapshots__/transform.test.ts.snap | 2822 ------------ .../lib/transaction_groups/fetcher.test.ts | 64 - .../server/lib/transaction_groups/fetcher.ts | 185 +- .../get_transaction_group_stats.ts | 144 + .../server/lib/transaction_groups/index.ts | 10 +- .../lib/transaction_groups/queries.test.ts | 8 +- .../lib/transaction_groups/transform.test.ts | 135 - .../lib/transaction_groups/transform.ts | 89 - .../get_local_filter_query.ts | 2 +- .../lib/ui_filters/local_ui_filters/index.ts | 1 + .../apm/typings/elasticsearch/aggregations.ts | 20 +- .../expectation/top_traces.expectation.json | 3970 ++++++++++++----- .../basic/tests/traces/top_traces.ts | 25 +- .../expectation/top_transaction_groups.json | 2639 ++++++++--- .../top_transaction_groups.ts | 25 +- 26 files changed, 5606 insertions(+), 5471 deletions(-) delete mode 100644 x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap delete mode 100644 x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap delete mode 100644 x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts create mode 100644 x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts delete mode 100644 x-pack/plugins/apm/server/lib/transaction_groups/transform.test.ts delete mode 100644 x-pack/plugins/apm/server/lib/transaction_groups/transform.ts diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx index 898e32f5c2c09..f54255ec0cd18 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ITransactionGroup } from '../../../../server/lib/transaction_groups/transform'; +import { TransactionGroup } from '../../../../server/lib/transaction_groups/fetcher'; import { fontSizes, truncate } from '../../../style/variables'; import { asMillisecondDuration } from '../../../utils/formatters'; import { EmptyMessage } from '../../shared/EmptyMessage'; @@ -24,11 +24,11 @@ const StyledTransactionLink = styled(TransactionDetailLink)` `; interface Props { - items: ITransactionGroup[]; + items: TransactionGroup[]; isLoading: boolean; } -const traceListColumns: Array> = [ +const traceListColumns: Array> = [ { field: 'name', name: i18n.translate('xpack.apm.tracesTable.nameColumnLabel', { @@ -36,8 +36,8 @@ const traceListColumns: Array> = [ }), width: '40%', sortable: true, - render: (name: string, { sample }: ITransactionGroup) => ( - + render: (_: string, { sample }: TransactionGroup) => ( + > = [ transactionName={sample.transaction.name} transactionType={sample.transaction.type} > - {name} + {sample.transaction.name} ), diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/List/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/List/index.tsx index ae1b07bde0c87..2b1c1b8e8c11c 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/List/index.tsx @@ -10,7 +10,7 @@ import React, { useMemo } from 'react'; import styled from 'styled-components'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ITransactionGroup } from '../../../../../server/lib/transaction_groups/transform'; +import { TransactionGroup } from '../../../../../server/lib/transaction_groups/fetcher'; import { fontFamilyCode, truncate } from '../../../../style/variables'; import { asDecimal, asMillisecondDuration } from '../../../../utils/formatters'; import { ImpactBar } from '../../../shared/ImpactBar'; @@ -25,12 +25,12 @@ const TransactionNameLink = styled(TransactionDetailLink)` `; interface Props { - items: ITransactionGroup[]; + items: TransactionGroup[]; isLoading: boolean; } export function TransactionList({ items, isLoading }: Props) { - const columns: Array> = useMemo( + const columns: Array> = useMemo( () => [ { field: 'name', @@ -39,11 +39,11 @@ export function TransactionList({ items, isLoading }: Props) { }), width: '50%', sortable: true, - render: (transactionName: string, { sample }: ITransactionGroup) => { + render: (_, { sample }: TransactionGroup) => { return ( - {transactionName || NOT_AVAILABLE_LABEL} + {sample.transaction.name || NOT_AVAILABLE_LABEL} ); diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/hooks/useTransactionList.ts index ed6bb9309a557..0ad221b95b4ff 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionList.ts @@ -4,45 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useMemo } from 'react'; import { IUrlParams } from '../context/UrlParamsContext/types'; import { useUiFilters } from '../context/UrlParamsContext'; import { useFetcher } from './useFetcher'; import { APIReturnType } from '../services/rest/createCallApmApi'; -const getRelativeImpact = ( - impact: number, - impactMin: number, - impactMax: number -) => - Math.max( - ((impact - impactMin) / Math.max(impactMax - impactMin, 1)) * 100, - 1 - ); - type TransactionsAPIResponse = APIReturnType< '/api/apm/services/{serviceName}/transaction_groups' >; -function getWithRelativeImpact(items: TransactionsAPIResponse['items']) { - const impacts = items - .map(({ impact }) => impact) - .filter((impact) => impact !== null) as number[]; - - const impactMin = Math.min(...impacts); - const impactMax = Math.max(...impacts); - - return items.map((item) => { - return { - ...item, - impactRelative: - item.impact !== null - ? getRelativeImpact(item.impact, impactMin, impactMax) - : null, - }; - }); -} - const DEFAULT_RESPONSE: TransactionsAPIResponse = { items: [], isAggregationAccurate: true, @@ -72,16 +42,8 @@ export function useTransactionList(urlParams: IUrlParams) { [serviceName, start, end, transactionType, uiFilters] ); - const memoizedData = useMemo( - () => ({ - items: getWithRelativeImpact(data.items), - isAggregationAccurate: data.isAggregationAccurate, - bucketSize: data.bucketSize, - }), - [data] - ); return { - data: memoizedData, + data, status, error, }; diff --git a/x-pack/plugins/apm/scripts/aggregate-latency-metrics/index.ts b/x-pack/plugins/apm/scripts/aggregate-latency-metrics/index.ts index 28b095335e93d..c3cf363cbec05 100644 --- a/x-pack/plugins/apm/scripts/aggregate-latency-metrics/index.ts +++ b/x-pack/plugins/apm/scripts/aggregate-latency-metrics/index.ts @@ -10,7 +10,7 @@ import pLimit from 'p-limit'; import pRetry from 'p-retry'; import { parse, format } from 'url'; import { set } from '@elastic/safer-lodash-set'; -import { unique, without, merge, flatten } from 'lodash'; +import { uniq, without, merge, flatten } from 'lodash'; import * as histogram from 'hdr-histogram-js'; import { ESSearchResponse } from '../../typings/elasticsearch'; import { @@ -114,8 +114,8 @@ export async function aggregateLatencyMetrics() { .filter(Boolean) as string[]; const fields = only.length - ? unique(only) - : without(unique([...include, ...defaultFields]), ...exclude); + ? uniq(only) + : without(uniq([...include, ...defaultFields]), ...exclude); const globalFilter = argv.filter ? JSON.parse(String(argv.filter)) : {}; diff --git a/x-pack/plugins/apm/scripts/shared/read-kibana-config.ts b/x-pack/plugins/apm/scripts/shared/read-kibana-config.ts index bc5f1afc63cac..fe226c8ab27d2 100644 --- a/x-pack/plugins/apm/scripts/shared/read-kibana-config.ts +++ b/x-pack/plugins/apm/scripts/shared/read-kibana-config.ts @@ -6,7 +6,7 @@ import path from 'path'; import fs from 'fs'; import yaml from 'js-yaml'; -import { identity, pick } from 'lodash'; +import { identity, pickBy } from 'lodash'; export type KibanaConfig = ReturnType; @@ -22,7 +22,7 @@ export const readKibanaConfig = () => { ) ) || {}) as {}; - const cliEsCredentials = pick( + const cliEsCredentials = pickBy( { 'elasticsearch.username': process.env.ELASTICSEARCH_USERNAME, 'elasticsearch.password': process.env.ELASTICSEARCH_PASSWORD, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index 1d14c509274a8..a922457b14cea 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -157,7 +157,7 @@ export function registerTransactionDurationAlertType({ const { agg } = response.aggregations; - const value = 'values' in agg ? agg.values[0] : agg?.value; + const value = 'values' in agg ? Object.values(agg.values)[0] : agg?.value; if (value && value > alertParams.threshold * 1000) { const alertInstance = services.alertInstanceFactory( diff --git a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts index 6de2728ee4366..895920a9b6c7d 100644 --- a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts +++ b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Unionize } from 'utility-types'; +import { Unionize, Overwrite } from 'utility-types'; +import { ESSearchRequest } from '../../../typings/elasticsearch'; import { Setup, SetupTimeRange, @@ -17,14 +18,28 @@ import { getMetricsProjection } from '../../../common/projections/metrics'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; import { AggregationOptionsByType } from '../../../typings/elasticsearch/aggregations'; -interface Aggs { - [key: string]: Unionize<{ - min: AggregationOptionsByType['min']; - max: AggregationOptionsByType['max']; - sum: AggregationOptionsByType['sum']; - avg: AggregationOptionsByType['avg']; - }>; -} +type MetricsAggregationMap = Unionize<{ + min: AggregationOptionsByType['min']; + max: AggregationOptionsByType['max']; + sum: AggregationOptionsByType['sum']; + avg: AggregationOptionsByType['avg']; +}>; + +type MetricAggs = Record; + +export type GenericMetricsRequest = Overwrite< + ESSearchRequest, + { + body: { + aggs: { + timeseriesData: { + date_histogram: AggregationOptionsByType['date_histogram']; + aggs: MetricAggs; + }; + } & MetricAggs; + }; + } +>; interface Filter { exists?: { @@ -35,7 +50,7 @@ interface Filter { }; } -export async function fetchAndTransformMetrics({ +export async function fetchAndTransformMetrics({ setup, serviceName, serviceNodeName, @@ -58,7 +73,7 @@ export async function fetchAndTransformMetrics({ serviceNodeName, }); - const params = mergeProjection(projection, { + const params: GenericMetricsRequest = mergeProjection(projection, { body: { size: 0, query: { diff --git a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts index affb7c2a12075..a191d5400e36c 100644 --- a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts @@ -4,40 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { Unionize, Overwrite } from 'utility-types'; import { ChartBase } from './types'; -import { - ESSearchResponse, - ESSearchRequest, -} from '../../../typings/elasticsearch'; -import { AggregationOptionsByType } from '../../../typings/elasticsearch/aggregations'; +import { ESSearchResponse } from '../../../typings/elasticsearch'; import { getVizColorForIndex } from '../../../common/viz_colors'; +import { GenericMetricsRequest } from './fetch_and_transform_metrics'; export type GenericMetricsChart = ReturnType< typeof transformDataToMetricsChart >; -interface MetricsAggregationMap { - min: AggregationOptionsByType['min']; - max: AggregationOptionsByType['max']; - sum: AggregationOptionsByType['sum']; - avg: AggregationOptionsByType['avg']; -} - -type GenericMetricsRequest = Overwrite< - ESSearchRequest, - { - body: { - aggs: { - timeseriesData: { - date_histogram: AggregationOptionsByType['date_histogram']; - aggs: Record>; - }; - } & Record>; - }; - } ->; - export function transformDataToMetricsChart( result: ESSearchResponse, chartBase: ChartBase @@ -51,11 +26,7 @@ export function transformDataToMetricsChart( yUnit: chartBase.yUnit, noHits: hits.total.value === 0, series: Object.keys(chartBase.series).map((seriesKey, i) => { - const overallValue = (aggregations?.[seriesKey] as - | { - value: number | null; - } - | undefined)?.value; + const overallValue = aggregations?.[seriesKey]?.value; return { title: chartBase.series[seriesKey].title, @@ -66,7 +37,7 @@ export function transformDataToMetricsChart( overallValue, data: timeseriesData?.buckets.map((bucket) => { - const { value } = bucket[seriesKey] as { value: number | null }; + const { value } = bucket[seriesKey]; const y = value === null || isNaN(value) ? null : value; return { x: bucket.key, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts index c28bcad841ffd..de699028f5675 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { arrayUnionToCallable } from '../../../../common/utils/array_union_to_callable'; import { PROCESSOR_EVENT, TRANSACTION_DURATION, @@ -187,7 +186,7 @@ export const getTransactionRates = async ({ const deltaAsMinutes = getDeltaAsMinutes(setup); - return arrayUnionToCallable(aggregations.services.buckets).map((bucket) => { + return aggregations.services.buckets.map((bucket) => { const transactionsPerMinute = bucket.doc_count / deltaAsMinutes; return { serviceName: bucket.key as string, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap deleted file mode 100644 index b354d3ed1f88d..0000000000000 --- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap +++ /dev/null @@ -1,228 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`transactionGroupsFetcher type: top_traces should call client.search with correct query 1`] = ` -Array [ - Array [ - Object { - "body": Object { - "aggs": Object { - "transaction_groups": Object { - "aggs": Object { - "avg": Object { - "avg": Object { - "field": "transaction.duration.us", - }, - }, - "p95": Object { - "percentiles": Object { - "field": "transaction.duration.us", - "hdr": Object { - "number_of_significant_value_digits": 2, - }, - "percents": Array [ - 95, - ], - }, - }, - "sample": Object { - "top_hits": Object { - "size": 1, - "sort": Array [ - Object { - "_score": "desc", - }, - Object { - "@timestamp": Object { - "order": "desc", - }, - }, - ], - }, - }, - "sum": Object { - "sum": Object { - "field": "transaction.duration.us", - }, - }, - }, - "composite": Object { - "size": 10000, - "sources": Array [ - Object { - "service": Object { - "terms": Object { - "field": "service.name", - }, - }, - }, - Object { - "transaction": Object { - "terms": Object { - "field": "transaction.name", - }, - }, - }, - ], - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - Object { - "term": Object { - "processor.event": "transaction", - }, - }, - Object { - "term": Object { - "service.environment": "test", - }, - }, - ], - "must_not": Array [ - Object { - "exists": Object { - "field": "parent.id", - }, - }, - ], - "should": Array [ - Object { - "term": Object { - "transaction.sampled": true, - }, - }, - ], - }, - }, - "size": 0, - }, - "index": "myIndex", - }, - ], -] -`; - -exports[`transactionGroupsFetcher type: top_transactions should call client.search with correct query 1`] = ` -Array [ - Array [ - Object { - "body": Object { - "aggs": Object { - "transaction_groups": Object { - "aggs": Object { - "avg": Object { - "avg": Object { - "field": "transaction.duration.us", - }, - }, - "p95": Object { - "percentiles": Object { - "field": "transaction.duration.us", - "hdr": Object { - "number_of_significant_value_digits": 2, - }, - "percents": Array [ - 95, - ], - }, - }, - "sample": Object { - "top_hits": Object { - "size": 1, - "sort": Array [ - Object { - "_score": "desc", - }, - Object { - "@timestamp": Object { - "order": "desc", - }, - }, - ], - }, - }, - "sum": Object { - "sum": Object { - "field": "transaction.duration.us", - }, - }, - }, - "composite": Object { - "size": 101, - "sources": Array [ - Object { - "transaction": Object { - "terms": Object { - "field": "transaction.name", - }, - }, - }, - ], - }, - }, - "transactions": Object { - "terms": Object { - "field": "transaction.name", - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - Object { - "term": Object { - "processor.event": "transaction", - }, - }, - Object { - "term": Object { - "transaction.type": "request", - }, - }, - Object { - "term": Object { - "service.name": "opbeans-node", - }, - }, - Object { - "term": Object { - "service.environment": "test", - }, - }, - ], - "should": Array [ - Object { - "term": Object { - "transaction.sampled": true, - }, - }, - ], - }, - }, - "size": 0, - }, - "index": "myIndex", - }, - ], -] -`; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap index 884a7d18cc4d4..deca46f4ebd0c 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap @@ -1,220 +1,479 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`transaction group queries fetches top traces 1`] = ` -Object { - "body": Object { - "aggs": Object { - "transaction_groups": Object { - "aggs": Object { - "avg": Object { - "avg": Object { - "field": "transaction.duration.us", - }, - }, - "p95": Object { - "percentiles": Object { - "field": "transaction.duration.us", - "hdr": Object { - "number_of_significant_value_digits": 2, +Array [ + Object { + "body": Object { + "aggs": Object { + "transaction_groups": Object { + "aggs": Object { + "sample": Object { + "top_hits": Object { + "size": 1, }, - "percents": Array [ - 95, - ], }, }, - "sample": Object { - "top_hits": Object { - "size": 1, - "sort": Array [ - Object { - "_score": "desc", + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "service.name": Object { + "terms": Object { + "field": "service.name", + }, }, - Object { - "@timestamp": Object { - "order": "desc", + }, + Object { + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", }, }, - ], + }, + ], + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + Object { + "term": Object { + "processor.event": "transaction", + }, + }, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + ], + "must_not": Array [ + Object { + "exists": Object { + "field": "parent.id", + }, + }, + ], + "should": Array [ + Object { + "term": Object { + "transaction.sampled": true, + }, }, + ], + }, + }, + "sort": Array [ + Object { + "_score": "desc", + }, + Object { + "@timestamp": Object { + "order": "desc", }, - "sum": Object { - "sum": Object { - "field": "transaction.duration.us", + }, + ], + }, + "index": "myIndex", + "size": 0, + }, + Object { + "body": Object { + "aggs": Object { + "transaction_groups": Object { + "aggs": Object { + "avg": Object { + "avg": Object { + "field": "transaction.duration.us", + }, }, }, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "service.name": Object { + "terms": Object { + "field": "service.name", + }, + }, + }, + Object { + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], + }, }, - "composite": Object { - "size": 10000, - "sources": Array [ + }, + "query": Object { + "bool": Object { + "filter": Array [ Object { - "service": Object { - "terms": Object { - "field": "service.name", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, }, }, }, Object { - "transaction": Object { - "terms": Object { - "field": "transaction.name", - }, + "term": Object { + "processor.event": "transaction", + }, + }, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + ], + "must_not": Array [ + Object { + "exists": Object { + "field": "parent.id", }, }, ], }, }, }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, + "index": "myIndex", + "size": 0, + }, + Object { + "body": Object { + "aggs": Object { + "transaction_groups": Object { + "aggs": Object { + "sum": Object { + "sum": Object { + "field": "transaction.duration.us", }, }, }, - Object { - "term": Object { - "processor.event": "transaction", - }, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "service.name": Object { + "terms": Object { + "field": "service.name", + }, + }, + }, + Object { + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], }, - Object { - "term": Object { - "my.custom.ui.filter": "foo-bar", + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, }, - }, - ], - "must_not": Array [ - Object { - "exists": Object { - "field": "parent.id", + Object { + "term": Object { + "processor.event": "transaction", + }, }, - }, - ], - "should": Array [ - Object { - "term": Object { - "transaction.sampled": true, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, }, - }, - ], + ], + "must_not": Array [ + Object { + "exists": Object { + "field": "parent.id", + }, + }, + ], + }, }, }, + "index": "myIndex", "size": 0, }, - "index": "myIndex", -} +] `; exports[`transaction group queries fetches top transactions 1`] = ` -Object { - "body": Object { - "aggs": Object { - "transaction_groups": Object { - "aggs": Object { - "avg": Object { - "avg": Object { - "field": "transaction.duration.us", - }, - }, - "p95": Object { - "percentiles": Object { - "field": "transaction.duration.us", - "hdr": Object { - "number_of_significant_value_digits": 2, +Array [ + Object { + "body": Object { + "aggs": Object { + "transaction_groups": Object { + "aggs": Object { + "sample": Object { + "top_hits": Object { + "size": 1, }, - "percents": Array [ - 95, - ], }, }, - "sample": Object { - "top_hits": Object { - "size": 1, - "sort": Array [ - Object { - "_score": "desc", - }, - Object { - "@timestamp": Object { - "order": "desc", - }, + "terms": Object { + "field": "transaction.name", + "size": 101, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, }, - ], + }, }, + Object { + "term": Object { + "processor.event": "transaction", + }, + }, + Object { + "term": Object { + "transaction.type": "bar", + }, + }, + Object { + "term": Object { + "service.name": "foo", + }, + }, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + ], + "should": Array [ + Object { + "term": Object { + "transaction.sampled": true, + }, + }, + ], + }, + }, + "sort": Array [ + Object { + "_score": "desc", + }, + Object { + "@timestamp": Object { + "order": "desc", }, - "sum": Object { - "sum": Object { - "field": "transaction.duration.us", + }, + ], + }, + "index": "myIndex", + "size": 0, + }, + Object { + "body": Object { + "aggs": Object { + "transaction_groups": Object { + "aggs": Object { + "avg": Object { + "avg": Object { + "field": "transaction.duration.us", + }, }, }, + "terms": Object { + "field": "transaction.name", + "size": 101, + }, }, - "composite": Object { - "size": 101, - "sources": Array [ + }, + "query": Object { + "bool": Object { + "filter": Array [ Object { - "transaction": Object { - "terms": Object { - "field": "transaction.name", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, }, }, }, + Object { + "term": Object { + "processor.event": "transaction", + }, + }, + Object { + "term": Object { + "transaction.type": "bar", + }, + }, + Object { + "term": Object { + "service.name": "foo", + }, + }, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, ], }, }, - "transactions": Object { - "terms": Object { - "field": "transaction.name", + }, + "index": "myIndex", + "size": 0, + }, + Object { + "body": Object { + "aggs": Object { + "transaction_groups": Object { + "aggs": Object { + "sum": Object { + "sum": Object { + "field": "transaction.duration.us", + }, + }, + }, + "terms": Object { + "field": "transaction.name", + "size": 101, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + Object { + "term": Object { + "processor.event": "transaction", + }, + }, + Object { + "term": Object { + "transaction.type": "bar", + }, + }, + Object { + "term": Object { + "service.name": "foo", + }, + }, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + ], }, }, }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, + "index": "myIndex", + "size": 0, + }, + Object { + "body": Object { + "aggs": Object { + "transaction_groups": Object { + "aggs": Object { + "p95": Object { + "percentiles": Object { + "field": "transaction.duration.us", + "hdr": Object { + "number_of_significant_value_digits": 2, + }, + "percents": Array [ + 95, + ], }, }, }, - Object { - "term": Object { - "processor.event": "transaction", - }, + "terms": Object { + "field": "transaction.name", + "size": 101, }, - Object { - "term": Object { - "transaction.type": "bar", + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, }, - }, - Object { - "term": Object { - "service.name": "foo", + Object { + "term": Object { + "processor.event": "transaction", + }, }, - }, - Object { - "term": Object { - "my.custom.ui.filter": "foo-bar", + Object { + "term": Object { + "transaction.type": "bar", + }, }, - }, - ], - "should": Array [ - Object { - "term": Object { - "transaction.sampled": true, + Object { + "term": Object { + "service.name": "foo", + }, }, - }, - ], + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + ], + }, }, }, + "index": "myIndex", "size": 0, }, - "index": "myIndex", -} +] `; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap deleted file mode 100644 index 66b805ab2efc1..0000000000000 --- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap +++ /dev/null @@ -1,2822 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`transactionGroupsTransformer should match snapshot 1`] = ` -Array [ - Object { - "averageResponseTime": 48021.972616494, - "impact": 100, - "name": "GET /api", - "p95": 67138.18364917398, - "sample": Object { - "@timestamp": "2018-11-18T20:53:44.070Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 5176, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3756, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-86c68779d8a65b06fb78e770ffc436a5-4aaea53dc1791183-01", - "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.6", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/types/3", - "hostname": "opbeans-node", - "pathname": "/api/types/3", - "port": "3000", - "protocol": "http:", - "raw": "/api/types/3", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-type": "application/json;charset=UTF-8", - "date": "Sun, 18 Nov 2018 20:53:43 GMT", - "transfer-encoding": "chunked", - "x-powered-by": "Express", - }, - "status_code": 404, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "parent": Object { - "id": "4aaea53dc1791183", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574424070007, - }, - "trace": Object { - "id": "86c68779d8a65b06fb78e770ffc436a5", - }, - "transaction": Object { - "duration": Object { - "us": 8684, - }, - "id": "a78bca581dcd8ff8", - "name": "GET /api", - "result": "HTTP 4xx", - "sampled": true, - "span_count": Object { - "started": 1, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 691926.3157894736, - }, - Object { - "averageResponseTime": 2651.8784461553205, - "impact": 15.770246496477105, - "name": "GET static file", - "p95": 6140.579335038363, - "sample": Object { - "@timestamp": "2018-11-18T20:53:43.304Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3756, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "accept": "*/*", - "host": "opbeans-node:3000", - "user-agent": "curl/7.38.0", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/", - "hostname": "opbeans-node", - "pathname": "/", - "port": "3000", - "protocol": "http:", - "raw": "/", - }, - }, - "response": Object { - "headers": Object { - "accept-ranges": "bytes", - "cache-control": "public, max-age=0", - "connection": "keep-alive", - "content-length": "640", - "content-type": "text/html; charset=UTF-8", - "date": "Sun, 18 Nov 2018 20:53:43 GMT", - "etag": "W/\\"280-1670775e878\\"", - "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574423304006, - }, - "trace": Object { - "id": "b303d2a4a007946b63b9db7fafe639a0", - }, - "transaction": Object { - "duration": Object { - "us": 1801, - }, - "id": "2869c13633534be5", - "name": "GET static file", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 0, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 1977031.5789473683, - }, - Object { - "averageResponseTime": 32554.36257814184, - "impact": 14.344171563678346, - "name": "GET /api/stats", - "p95": 59356.73611111111, - "sample": Object { - "@timestamp": "2018-11-18T20:53:42.560Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 207, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3756, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-63ccc3b0929dafb7f2fbcabdc7f7af25-821a787e73ab1563-01", - "host": "opbeans-node:3000", - "if-none-match": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", - "referer": "http://opbeans-node:3000/dashboard", - "user-agent": "Chromeless 1.4.0", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.7", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/stats", - "hostname": "opbeans-node", - "pathname": "/api/stats", - "port": "3000", - "protocol": "http:", - "raw": "/api/stats", - }, - }, - "response": Object { - "headers": Object { - "connection": "keep-alive", - "date": "Sun, 18 Nov 2018 20:53:42 GMT", - "etag": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", - "x-powered-by": "Express", - }, - "status_code": 304, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "parent": Object { - "id": "821a787e73ab1563", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574422560002, - }, - "trace": Object { - "id": "63ccc3b0929dafb7f2fbcabdc7f7af25", - }, - "transaction": Object { - "duration": Object { - "us": 28753, - }, - "id": "fb754e7628da2fb5", - "name": "GET /api/stats", - "result": "HTTP 3xx", - "sampled": true, - "span_count": Object { - "started": 7, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 146494.73684210525, - }, - Object { - "averageResponseTime": 32159.926322043968, - "impact": 10.27904952170656, - "name": "GET /api/customers", - "p95": 59845.85714285714, - "sample": Object { - "@timestamp": "2018-11-18T20:53:21.180Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 2531, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3710, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-541025da8ecc2f51f21c1a4ad6992b77-ca18d9d4c3879519-01", - "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.6", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/customers", - "hostname": "opbeans-node", - "pathname": "/api/customers", - "port": "3000", - "protocol": "http:", - "raw": "/api/customers", - }, - }, - "response": Object { - "headers": Object { - "connection": "keep-alive", - "content-length": "186769", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:21 GMT", - "etag": "W/\\"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "parent": Object { - "id": "ca18d9d4c3879519", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574401180002, - }, - "trace": Object { - "id": "541025da8ecc2f51f21c1a4ad6992b77", - }, - "transaction": Object { - "duration": Object { - "us": 18077, - }, - "id": "94852b9dd1075982", - "name": "GET /api/customers", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 2, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 106294.73684210525, - }, - Object { - "averageResponseTime": 33265.03326147213, - "impact": 10.256357027376065, - "name": "GET /api/orders", - "p95": 58827.489999999976, - "sample": Object { - "@timestamp": "2018-11-18T20:53:40.973Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 408, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3756, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/orders", - "hostname": "opbeans-node", - "pathname": "/api/orders", - "port": "3000", - "protocol": "http:", - "raw": "/api/orders", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "103612", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:40 GMT", - "etag": "W/\\"194bc-cOw6+iRf7XCeqMXHrle3IOig7tY\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574420973006, - }, - "trace": Object { - "id": "0afce85f593cbbdd09949936fe964f0f", - }, - "transaction": Object { - "duration": Object { - "us": 23040, - }, - "id": "89f200353eb50539", - "name": "GET /api/orders", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 2, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 102536.84210526315, - }, - Object { - "averageResponseTime": 27516.89144558744, - "impact": 9.651458992731666, - "name": "GET /api/products/top", - "p95": 56064.679999999986, - "sample": Object { - "@timestamp": "2018-11-18T20:52:57.316Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 5113, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3686, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-74f12e705936d66350f4741ebeb55189-fcebe94cd2136215-01", - "host": "opbeans-node:3000", - "referer": "http://opbeans-node:3000/dashboard", - "user-agent": "Chromeless 1.4.0", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.7", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/products/top", - "hostname": "opbeans-node", - "pathname": "/api/products/top", - "port": "3000", - "protocol": "http:", - "raw": "/api/products/top", - }, - }, - "response": Object { - "headers": Object { - "connection": "keep-alive", - "content-length": "282", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:57 GMT", - "etag": "W/\\"11a-lcI9zuMZYYsDRpEZgYqDYr96cKM\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "parent": Object { - "id": "fcebe94cd2136215", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574377316005, - }, - "trace": Object { - "id": "74f12e705936d66350f4741ebeb55189", - }, - "transaction": Object { - "duration": Object { - "us": 48781, - }, - "id": "be4bd5475d5d9e6f", - "name": "GET /api/products/top", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 4, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 116652.63157894736, - }, - Object { - "averageResponseTime": 12683.190864600327, - "impact": 4.4239778504968, - "name": "GET /api/products", - "p95": 35009.67999999999, - "sample": Object { - "@timestamp": "2018-11-18T20:53:43.477Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 2857, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3756, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/products", - "hostname": "opbeans-node", - "pathname": "/api/products", - "port": "3000", - "protocol": "http:", - "raw": "/api/products", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "1023", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:43 GMT", - "etag": "W/\\"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574423477006, - }, - "trace": Object { - "id": "bee00a8efb523ca4b72adad57f7caba3", - }, - "transaction": Object { - "duration": Object { - "us": 6915, - }, - "id": "d8fc6d3b8707b64c", - "name": "GET /api/products", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 2, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 116147.36842105263, - }, - Object { - "averageResponseTime": 255966.30555555556, - "impact": 4.3693406535517445, - "name": "POST /api/orders", - "p95": 320238.5, - "sample": Object { - "@timestamp": "2018-11-18T20:43:32.010Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 4669, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 2413, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "body": "[REDACTED]", - "headers": Object { - "accept": "application/json", - "connection": "close", - "content-length": "129", - "content-type": "application/json", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "POST", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/orders", - "hostname": "opbeans-node", - "pathname": "/api/orders", - "port": "3000", - "protocol": "http:", - "raw": "/api/orders", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "13", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:43:32 GMT", - "etag": "W/\\"d-g9K2iK4ordyN88lGL4LmPlYNfhc\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542573812010006, - }, - "trace": Object { - "id": "2b1252a338249daeecf6afb0c236e31b", - }, - "transaction": Object { - "duration": Object { - "us": 291572, - }, - "id": "2c9f39e9ec4a0111", - "name": "POST /api/orders", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 16, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 5684.210526315789, - }, - Object { - "averageResponseTime": 17189.329210275926, - "impact": 3.424381787142002, - "name": "GET /api/products/:id/customers", - "p95": 39284.79999999999, - "sample": Object { - "@timestamp": "2018-11-18T20:48:24.769Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 1735, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3100, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-28f178c354d17f400dea04bc4a7b3c57-68f5d1607cac7779-01", - "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.6", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/products/2/customers", - "hostname": "opbeans-node", - "pathname": "/api/products/2/customers", - "port": "3000", - "protocol": "http:", - "raw": "/api/products/2/customers", - }, - }, - "response": Object { - "headers": Object { - "connection": "keep-alive", - "content-length": "186570", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:48:24 GMT", - "etag": "W/\\"2d8ca-Z9NzuHyGyxwtzpOkcIxBvzm24iw\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "parent": Object { - "id": "68f5d1607cac7779", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574104769029, - }, - "trace": Object { - "id": "28f178c354d17f400dea04bc4a7b3c57", - }, - "transaction": Object { - "duration": Object { - "us": 49338, - }, - "id": "2a87ae20ad04ee0c", - "name": "GET /api/products/:id/customers", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 1, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 66378.94736842105, - }, - Object { - "averageResponseTime": 11257.757916666667, - "impact": 2.558180605569336, - "name": "GET /api/types", - "p95": 35222.944444444445, - "sample": Object { - "@timestamp": "2018-11-18T20:53:44.978Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 2193, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3756, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/types", - "hostname": "opbeans-node", - "pathname": "/api/types", - "port": "3000", - "protocol": "http:", - "raw": "/api/types", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "112", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:44 GMT", - "etag": "W/\\"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574424978005, - }, - "trace": Object { - "id": "0d84126973411c19b470f2d9eea958d3", - }, - "transaction": Object { - "duration": Object { - "us": 7891, - }, - "id": "0f10668e4fb3adc7", - "name": "GET /api/types", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 2, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 75789.47368421052, - }, - Object { - "averageResponseTime": 3504.5108924806746, - "impact": 2.3600993453143766, - "name": "GET *", - "p95": 11431.738095238095, - "sample": Object { - "@timestamp": "2018-11-18T20:53:42.493Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 6446, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3756, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "host": "opbeans-node:3000", - "if-modified-since": "Mon, 12 Nov 2018 10:27:07 GMT", - "if-none-match": "W/\\"280-1670775e878\\"", - "upgrade-insecure-requests": "1", - "user-agent": "Chromeless 1.4.0", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.7", - }, - "url": Object { - "full": "http://opbeans-node:3000/dashboard", - "hostname": "opbeans-node", - "pathname": "/dashboard", - "port": "3000", - "protocol": "http:", - "raw": "/dashboard", - }, - }, - "response": Object { - "headers": Object { - "accept-ranges": "bytes", - "cache-control": "public, max-age=0", - "connection": "keep-alive", - "date": "Sun, 18 Nov 2018 20:53:42 GMT", - "etag": "W/\\"280-1670775e878\\"", - "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", - "x-powered-by": "Express", - }, - "status_code": 304, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574422493006, - }, - "trace": Object { - "id": "7efb6ade88cdea20cd96ca482681cde7", - }, - "transaction": Object { - "duration": Object { - "us": 1901, - }, - "id": "f5fc4621949b63fb", - "name": "GET *", - "result": "HTTP 3xx", - "sampled": true, - "span_count": Object { - "started": 0, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 224684.21052631576, - }, - Object { - "averageResponseTime": 32387.73641304348, - "impact": 2.2558112380477584, - "name": "GET /log-error", - "p95": 40061.1, - "sample": Object { - "@timestamp": "2018-11-18T20:52:51.462Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 4877, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3659, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/log-error", - "hostname": "opbeans-node", - "pathname": "/log-error", - "port": "3000", - "protocol": "http:", - "raw": "/log-error", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "24", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:51 GMT", - "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", - "x-powered-by": "Express", - }, - "status_code": 500, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574371462005, - }, - "trace": Object { - "id": "15366d65659b5fc8f67ff127391b3aff", - }, - "transaction": Object { - "duration": Object { - "us": 33367, - }, - "id": "ec9c465c5042ded8", - "name": "GET /log-error", - "result": "HTTP 5xx", - "sampled": true, - "span_count": Object { - "started": 0, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 23242.105263157893, - }, - Object { - "averageResponseTime": 32900.72714285714, - "impact": 2.1791207411745854, - "name": "GET /log-message", - "p95": 40444, - "sample": Object { - "@timestamp": "2018-11-18T20:49:09.225Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 321, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3142, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/log-message", - "hostname": "opbeans-node", - "pathname": "/log-message", - "port": "3000", - "protocol": "http:", - "raw": "/log-message", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "24", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:49:09 GMT", - "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", - "x-powered-by": "Express", - }, - "status_code": 500, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574149225004, - }, - "trace": Object { - "id": "ba18b741cdd3ac83eca89a5fede47577", - }, - "transaction": Object { - "duration": Object { - "us": 32381, - }, - "id": "b9a8f96d7554d09f", - "name": "GET /log-message", - "result": "HTTP 5xx", - "sampled": true, - "span_count": Object { - "started": 0, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 22105.263157894737, - }, - Object { - "averageResponseTime": 10548.218597063622, - "impact": 1.8338763992340905, - "name": "GET /api/products/:id", - "p95": 28413.383333333328, - "sample": Object { - "@timestamp": "2018-11-18T20:52:57.963Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 7184, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3686, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/products/3", - "hostname": "opbeans-node", - "pathname": "/api/products/3", - "port": "3000", - "protocol": "http:", - "raw": "/api/products/3", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "231", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:57 GMT", - "etag": "W/\\"e7-kkuzj37GZDzXDh0CWqh5Gan0VO4\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574377963005, - }, - "trace": Object { - "id": "ca86ec845e412e4b4506a715d51548ec", - }, - "transaction": Object { - "duration": Object { - "us": 6959, - }, - "id": "d324897ffb7ebcdc", - "name": "GET /api/products/:id", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 1, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 58073.68421052631, - }, - Object { - "averageResponseTime": 9868.217894736843, - "impact": 1.7722323960215767, - "name": "GET /api/customers/:id", - "p95": 27486.5, - "sample": Object { - "@timestamp": "2018-11-18T20:52:56.797Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 8225, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3686, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-e6140d30363f18b585f5d3b753f4d025-aa82e2c847265626-01", - "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.6", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/customers/700", - "hostname": "opbeans-node", - "pathname": "/api/customers/700", - "port": "3000", - "protocol": "http:", - "raw": "/api/customers/700", - }, - }, - "response": Object { - "headers": Object { - "connection": "keep-alive", - "content-length": "193", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:56 GMT", - "etag": "W/\\"c1-LbuhkuLzFyZ0H+7+JQGA5b0kvNs\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "parent": Object { - "id": "aa82e2c847265626", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574376797031, - }, - "trace": Object { - "id": "e6140d30363f18b585f5d3b753f4d025", - }, - "transaction": Object { - "duration": Object { - "us": 9735, - }, - "id": "60e230d12f3f0960", - "name": "GET /api/customers/:id", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 1, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 59999.99999999999, - }, - Object { - "averageResponseTime": 12763.68806073154, - "impact": 1.7479924334286208, - "name": "GET /api/types/:id", - "p95": 30576.749999999996, - "sample": Object { - "@timestamp": "2018-11-18T20:53:35.967Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 5345, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3756, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/types/1", - "hostname": "opbeans-node", - "pathname": "/api/types/1", - "port": "3000", - "protocol": "http:", - "raw": "/api/types/1", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "217", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:35 GMT", - "etag": "W/\\"d9-cebOOHODBQMZd1wt+ZZBaSPgQLQ\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574415967005, - }, - "trace": Object { - "id": "2223b30b5cbaf2e221fcf70ac6d9abbe", - }, - "transaction": Object { - "duration": Object { - "us": 13064, - }, - "id": "053436abacdec0a4", - "name": "GET /api/types/:id", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 2, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 45757.8947368421, - }, - Object { - "averageResponseTime": 10584.05144193297, - "impact": 1.280810614916383, - "name": "GET /api/orders/:id", - "p95": 26555.399999999998, - "sample": Object { - "@timestamp": "2018-11-18T20:51:36.949Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 5999, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3475, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/orders/183", - "hostname": "opbeans-node", - "pathname": "/api/orders/183", - "port": "3000", - "protocol": "http:", - "raw": "/api/orders/183", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "0", - "date": "Sun, 18 Nov 2018 20:51:36 GMT", - "x-powered-by": "Express", - }, - "status_code": 404, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574296949004, - }, - "trace": Object { - "id": "dab6421fa44a6869887e0edf32e1ad6f", - }, - "transaction": Object { - "duration": Object { - "us": 5906, - }, - "id": "937ef5588454f74a", - "name": "GET /api/orders/:id", - "result": "HTTP 4xx", - "sampled": true, - "span_count": Object { - "started": 1, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 40515.789473684206, - }, - Object { - "averageResponseTime": 1422.926672899693, - "impact": 1.0027124806135428, - "name": "GET unknown route", - "p95": 2311.885238095238, - "sample": Object { - "@timestamp": "2018-11-18T20:53:42.504Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3756, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "host": "opbeans-node:3000", - "referer": "http://opbeans-node:3000/dashboard", - "user-agent": "Chromeless 1.4.0", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.7", - }, - "url": Object { - "full": "http://opbeans-node:3000/rum-config.js", - "hostname": "opbeans-node", - "pathname": "/rum-config.js", - "port": "3000", - "protocol": "http:", - "raw": "/rum-config.js", - }, - }, - "response": Object { - "headers": Object { - "connection": "keep-alive", - "content-length": "172", - "content-type": "text/javascript", - "date": "Sun, 18 Nov 2018 20:53:42 GMT", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574422504004, - }, - "trace": Object { - "id": "4399e7233e6e7b77e70c2fff111b8f28", - }, - "transaction": Object { - "duration": Object { - "us": 911, - }, - "id": "107881ae2be1b56d", - "name": "GET unknown route", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 0, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 236431.5789473684, - }, - Object { - "averageResponseTime": 21331.714285714286, - "impact": 0.28817487960409877, - "name": "POST /api", - "p95": 30938, - "sample": Object { - "@timestamp": "2018-11-18T20:29:42.751Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 2927, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 546, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "body": "[REDACTED]", - "headers": Object { - "accept": "application/json", - "connection": "close", - "content-length": "129", - "content-type": "application/json", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "POST", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/api/orders", - "hostname": "opbeans-node", - "pathname": "/api/orders", - "port": "3000", - "protocol": "http:", - "raw": "/api/orders", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "0", - "date": "Sun, 18 Nov 2018 20:29:42 GMT", - "x-powered-by": "Express", - }, - "status_code": 400, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542572982751005, - }, - "trace": Object { - "id": "8ed4d94ec8fc11b1ea1b0aa59c2320ff", - }, - "transaction": Object { - "duration": Object { - "us": 21083, - }, - "id": "d67c2f7aa897110c", - "name": "POST /api", - "result": "HTTP 4xx", - "sampled": true, - "span_count": Object { - "started": 1, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 4642.105263157894, - }, - Object { - "averageResponseTime": 4694.005586592179, - "impact": 0.1498515000753004, - "name": "GET /is-it-coffee-time", - "p95": 11022.99999999992, - "sample": Object { - "@timestamp": "2018-11-18T20:46:19.317Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 8593, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 2760, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/is-it-coffee-time", - "hostname": "opbeans-node", - "pathname": "/is-it-coffee-time", - "port": "3000", - "protocol": "http:", - "raw": "/is-it-coffee-time", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "148", - "content-security-policy": "default-src 'self'", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:46:19 GMT", - "x-content-type-options": "nosniff", - "x-powered-by": "Express", - }, - "status_code": 500, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542573979317007, - }, - "trace": Object { - "id": "821812b416de4c73ced87f8777fa46a6", - }, - "transaction": Object { - "duration": Object { - "us": 4253, - }, - "id": "319a5c555a1ab207", - "name": "GET /is-it-coffee-time", - "result": "HTTP 5xx", - "sampled": true, - "span_count": Object { - "started": 0, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 11305.263157894737, - }, - Object { - "averageResponseTime": 4549.889880952381, - "impact": 0.13543365054509587, - "name": "GET /throw-error", - "p95": 7719.700000000001, - "sample": Object { - "@timestamp": "2018-11-18T20:47:10.714Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 7220, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 2895, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "GET", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/throw-error", - "hostname": "opbeans-node", - "pathname": "/throw-error", - "port": "3000", - "protocol": "http:", - "raw": "/throw-error", - }, - }, - "response": Object { - "headers": Object { - "connection": "close", - "content-length": "148", - "content-security-policy": "default-src 'self'", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:47:10 GMT", - "x-content-type-options": "nosniff", - "x-powered-by": "Express", - }, - "status_code": 500, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574030714012, - }, - "trace": Object { - "id": "6c0ef23e1f963f304ce440a909914d35", - }, - "transaction": Object { - "duration": Object { - "us": 4458, - }, - "id": "ecd187dc53f09fbd", - "name": "GET /throw-error", - "result": "HTTP 5xx", - "sampled": true, - "span_count": Object { - "started": 0, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 10610.526315789473, - }, - Object { - "averageResponseTime": 2742.4615384615386, - "impact": 0.08501028923348058, - "name": "OPTIONS unknown route", - "p95": 4370.000000000002, - "sample": Object { - "@timestamp": "2018-11-18T20:49:00.707Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 3775, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 3142, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "headers": Object { - "connection": "close", - "content-length": "0", - "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", - }, - "http_version": "1.1", - "method": "OPTIONS", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.10", - }, - "url": Object { - "full": "http://opbeans-node:3000/", - "hostname": "opbeans-node", - "pathname": "/", - "port": "3000", - "protocol": "http:", - "raw": "/", - }, - }, - "response": Object { - "headers": Object { - "allow": "GET,HEAD", - "connection": "close", - "content-length": "8", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:49:00 GMT", - "etag": "W/\\"8-ZRAf8oNBS3Bjb/SU2GYZCmbtmXg\\"", - "x-powered-by": "Express", - }, - "status_code": 200, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542574140707006, - }, - "trace": Object { - "id": "469e3e5f91ffe3195a8e58cdd1cdefa8", - }, - "transaction": Object { - "duration": Object { - "us": 2371, - }, - "id": "a8c87ebc7ec68bc0", - "name": "OPTIONS unknown route", - "result": "HTTP 2xx", - "sampled": true, - "span_count": Object { - "started": 0, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 11494.736842105262, - }, - Object { - "averageResponseTime": 5192.9, - "impact": 0, - "name": "POST unknown route", - "p95": 13230.5, - "sample": Object { - "@timestamp": "2018-11-18T18:43:50.994Z", - "agent": Object { - "hostname": "b359e3afece8", - "type": "apm-server", - "version": "7.0.0-alpha1", - }, - "context": Object { - "custom": Object { - "containerId": 6102, - }, - "process": Object { - "argv": Array [ - "/usr/local/bin/node", - "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", - ], - "pid": 19196, - "ppid": 1, - "title": "node /app/server.js", - }, - "request": Object { - "body": "[REDACTED]", - "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "content-length": "380", - "content-type": "multipart/form-data; boundary=2b2e40be188a4cb5a56c05a0c182f6c9", - "elastic-apm-traceparent": "00-19688959ea6cbccda8013c11566ea329-1fc3665eef2dcdfc-01", - "host": "172.18.0.9:3000", - "user-agent": "Python/3.7 aiohttp/3.3.2", - "x-forwarded-for": "172.18.0.11", - }, - "http_version": "1.1", - "method": "POST", - "socket": Object { - "encrypted": false, - "remote_address": "::ffff:172.18.0.9", - }, - "url": Object { - "full": "http://172.18.0.9:3000/api/orders/csv", - "hostname": "172.18.0.9", - "pathname": "/api/orders/csv", - "port": "3000", - "protocol": "http:", - "raw": "/api/orders/csv", - }, - }, - "response": Object { - "headers": Object { - "connection": "keep-alive", - "content-length": "154", - "content-security-policy": "default-src 'self'", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 18:43:50 GMT", - "x-content-type-options": "nosniff", - "x-powered-by": "Express", - }, - "status_code": 404, - }, - "service": Object { - "agent": Object { - "name": "nodejs", - "version": "1.14.2", - }, - "language": Object { - "name": "javascript", - }, - "name": "opbeans-node", - "runtime": Object { - "name": "node", - "version": "8.12.0", - }, - "version": "1.0.0", - }, - "system": Object { - "architecture": "x64", - "hostname": "98195610c255", - "ip": "172.18.0.10", - "platform": "linux", - }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, - }, - "host": Object { - "name": "b359e3afece8", - }, - "parent": Object { - "id": "1fc3665eef2dcdfc", - }, - "processor": Object { - "event": "transaction", - "name": "transaction", - }, - "timestamp": Object { - "us": 1542566630994005, - }, - "trace": Object { - "id": "19688959ea6cbccda8013c11566ea329", - }, - "transaction": Object { - "duration": Object { - "us": 3467, - }, - "id": "92c3ceea57899061", - "name": "POST unknown route", - "result": "HTTP 4xx", - "sampled": true, - "span_count": Object { - "started": 0, - }, - "type": "request", - }, - }, - "transactionsPerMinute": 631.578947368421, - }, -] -`; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts deleted file mode 100644 index a26c3d85a3fc4..0000000000000 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { transactionGroupsFetcher } from './fetcher'; -import { APMConfig } from '../..'; - -function getSetup() { - return { - start: 1528113600000, - end: 1528977600000, - client: { - search: jest.fn(), - } as any, - internalClient: { - search: jest.fn(), - } as any, - config: { - 'xpack.apm.ui.transactionGroupBucketSize': 100, - } as APMConfig, - uiFiltersES: [{ term: { 'service.environment': 'test' } }], - indices: { - 'apm_oss.sourcemapIndices': 'myIndex', - 'apm_oss.errorIndices': 'myIndex', - 'apm_oss.onboardingIndices': 'myIndex', - 'apm_oss.spanIndices': 'myIndex', - 'apm_oss.transactionIndices': 'myIndex', - 'apm_oss.metricsIndices': 'myIndex', - apmAgentConfigurationIndex: 'myIndex', - apmCustomLinkIndex: 'myIndex', - }, - dynamicIndexPattern: null as any, - }; -} - -describe('transactionGroupsFetcher', () => { - describe('type: top_traces', () => { - it('should call client.search with correct query', async () => { - const setup = getSetup(); - const bucketSize = 100; - await transactionGroupsFetcher({ type: 'top_traces' }, setup, bucketSize); - expect(setup.client.search.mock.calls).toMatchSnapshot(); - }); - }); - - describe('type: top_transactions', () => { - it('should call client.search with correct query', async () => { - const setup = getSetup(); - const bucketSize = 100; - await transactionGroupsFetcher( - { - type: 'top_transactions', - serviceName: 'opbeans-node', - transactionType: 'request', - }, - setup, - bucketSize - ); - expect(setup.client.search.mock.calls).toMatchSnapshot(); - }); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts index a5cc74b18a7ef..73bf1d01924e7 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -3,23 +3,31 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { take, sortBy } from 'lodash'; +import { Unionize } from 'utility-types'; +import moment from 'moment'; +import { joinByKey } from '../../../common/utils/join_by_key'; +import { ESSearchRequest } from '../../../typings/elasticsearch'; import { SERVICE_NAME, - TRANSACTION_DURATION, - TRANSACTION_SAMPLED, TRANSACTION_NAME, } from '../../../common/elasticsearch_fieldnames'; import { getTransactionGroupsProjection } from '../../../common/projections/transaction_groups'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; import { PromiseReturnType } from '../../../../observability/typings/common'; -import { SortOptions } from '../../../typings/elasticsearch/aggregations'; +import { AggregationOptionsByType } from '../../../typings/elasticsearch/aggregations'; import { Transaction } from '../../../typings/es_schemas/ui/transaction'; import { Setup, SetupTimeRange, SetupUIFilters, } from '../helpers/setup_request'; +import { + getSamples, + getAverages, + getSums, + getPercentiles, +} from './get_transaction_group_stats'; interface TopTransactionOptions { type: 'top_transactions'; @@ -36,68 +44,149 @@ interface TopTraceOptions { export type Options = TopTransactionOptions | TopTraceOptions; export type ESResponse = PromiseReturnType; + +export type TransactionGroupRequestBase = ESSearchRequest & { + body: { + aggs: { + transaction_groups: Unionize< + Pick + >; + }; + }; +}; + +export type TransactionGroupSetup = Setup & SetupTimeRange & SetupUIFilters; + +function getItemsWithRelativeImpact( + setup: TransactionGroupSetup, + items: Array<{ + sum?: number | null; + key: string | Record; + avg?: number | null; + count?: number | null; + p95?: number; + sample?: Transaction; + }> +) { + const values = items + .map(({ sum }) => sum) + .filter((value) => value !== null) as number[]; + + const max = Math.max(...values); + const min = Math.min(...values); + + const duration = moment.duration(setup.end - setup.start); + const minutes = duration.asMinutes(); + + const itemsWithRelativeImpact: TransactionGroup[] = items + .map((item) => { + return { + key: item.key, + averageResponseTime: item.avg, + transactionsPerMinute: (item.count ?? 0) / minutes, + impact: + item.sum !== null && item.sum !== undefined + ? ((item.sum - min) / (max - min)) * 100 || 0 + : 0, + p95: item.p95, + sample: item.sample!, + }; + }) + .filter((item) => item.sample); + + return itemsWithRelativeImpact; +} + export async function transactionGroupsFetcher( options: Options, - setup: Setup & SetupTimeRange & SetupUIFilters, + setup: TransactionGroupSetup, bucketSize: number ) { - const { client } = setup; - const projection = getTransactionGroupsProjection({ setup, options, }); - const sort: SortOptions = [ - { _score: 'desc' as const }, // sort by _score to ensure that buckets with sampled:true ends up on top - { '@timestamp': { order: 'desc' as const } }, - ]; - const isTopTraces = options.type === 'top_traces'; - if (isTopTraces) { - // Delete the projection aggregation when searching for traces, as it should use the combined aggregation instead - delete projection.body.aggs; - } + delete projection.body.aggs; + + // traces overview is hardcoded to 10000 + // transactions overview: 1 extra bucket is added to check whether the total number of buckets exceed the specified bucket size. + const expectedBucketSize = isTopTraces ? 10000 : bucketSize; + const size = isTopTraces ? 10000 : expectedBucketSize + 1; - const params = mergeProjection(projection, { + const request = mergeProjection(projection, { + size: 0, body: { - size: 0, - query: { - bool: { - // prefer sampled transactions - should: [{ term: { [TRANSACTION_SAMPLED]: true } }], - }, - }, aggs: { transaction_groups: { - composite: { - // traces overview is hardcoded to 10000 - // transactions overview: 1 extra bucket is added to check whether the total number of buckets exceed the specified bucket size. - size: isTopTraces ? 10000 : bucketSize + 1, - sources: [ - ...(isTopTraces - ? [{ service: { terms: { field: SERVICE_NAME } } }] - : []), - { transaction: { terms: { field: TRANSACTION_NAME } } }, - ], - }, - aggs: { - sample: { top_hits: { size: 1, sort } }, - avg: { avg: { field: TRANSACTION_DURATION } }, - p95: { - percentiles: { - field: TRANSACTION_DURATION, - percents: [95], - hdr: { number_of_significant_value_digits: 2 }, - }, - }, - sum: { sum: { field: TRANSACTION_DURATION } }, - }, + ...(isTopTraces + ? { + composite: { + sources: [ + { [SERVICE_NAME]: { terms: { field: SERVICE_NAME } } }, + { + [TRANSACTION_NAME]: { + terms: { field: TRANSACTION_NAME }, + }, + }, + ], + size, + }, + } + : { + terms: { + field: TRANSACTION_NAME, + size, + }, + }), }, }, }, }); - return client.search(params); + const params = { + request, + setup, + }; + + const [samples, averages, sums, percentiles] = await Promise.all([ + getSamples(params), + getAverages(params), + getSums(params), + !isTopTraces ? getPercentiles(params) : Promise.resolve(undefined), + ]); + + const stats = [ + ...samples, + ...averages, + ...sums, + ...(percentiles ? percentiles : []), + ]; + + const items = joinByKey(stats, 'key'); + + const itemsWithRelativeImpact = getItemsWithRelativeImpact(setup, items); + + return { + items: take( + // sort by impact by default so most impactful services are not cut off + sortBy(itemsWithRelativeImpact, 'impact').reverse(), + expectedBucketSize + ), + // The aggregation is considered accurate if the configured bucket size is larger or equal to the number of buckets returned + // the actual number of buckets retrieved are `bucketsize + 1` to detect whether it's above the limit + isAggregationAccurate: expectedBucketSize >= itemsWithRelativeImpact.length, + bucketSize, + }; +} + +export interface TransactionGroup { + key: Record | string; + averageResponseTime: number | null | undefined; + transactionsPerMinute: number; + p95: number | undefined; + impact: number; + sample: Transaction; } diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts new file mode 100644 index 0000000000000..59fb370113ec2 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { merge } from 'lodash'; +import { arrayUnionToCallable } from '../../../common/utils/array_union_to_callable'; +import { Transaction } from '../../../typings/es_schemas/ui/transaction'; +import { + TRANSACTION_SAMPLED, + TRANSACTION_DURATION, +} from '../../../common/elasticsearch_fieldnames'; +import { + AggregationInputMap, + SortOptions, +} from '../../../typings/elasticsearch/aggregations'; +import { TransactionGroupRequestBase, TransactionGroupSetup } from './fetcher'; + +interface MetricParams { + request: TransactionGroupRequestBase; + setup: TransactionGroupSetup; +} + +type BucketKey = string | Record; + +function mergeRequestWithAggs< + TRequestBase extends TransactionGroupRequestBase, + TInputMap extends AggregationInputMap +>(request: TRequestBase, aggs: TInputMap) { + return merge({}, request, { + body: { + aggs: { + transaction_groups: { + aggs, + }, + }, + }, + }); +} + +export async function getSamples({ request, setup }: MetricParams) { + const params = mergeRequestWithAggs(request, { + sample: { + top_hits: { + size: 1, + }, + }, + }); + + const sort: SortOptions = [ + { _score: 'desc' as const }, // sort by _score to ensure that buckets with sampled:true ends up on top + { '@timestamp': { order: 'desc' as const } }, + ]; + + const response = await setup.client.search({ + ...params, + body: { + ...params.body, + query: { + ...params.body.query, + bool: { + ...params.body.query.bool, + should: [{ term: { [TRANSACTION_SAMPLED]: true } }], + }, + }, + sort, + }, + }); + + return arrayUnionToCallable( + response.aggregations?.transaction_groups.buckets ?? [] + ).map((bucket) => { + return { + key: bucket.key as BucketKey, + count: bucket.doc_count, + sample: bucket.sample.hits.hits[0]._source as Transaction, + }; + }); +} + +export async function getAverages({ request, setup }: MetricParams) { + const params = mergeRequestWithAggs(request, { + avg: { + avg: { + field: TRANSACTION_DURATION, + }, + }, + }); + + const response = await setup.client.search(params); + + return arrayUnionToCallable( + response.aggregations?.transaction_groups.buckets ?? [] + ).map((bucket) => { + return { + key: bucket.key as BucketKey, + avg: bucket.avg.value, + }; + }); +} + +export async function getSums({ request, setup }: MetricParams) { + const params = mergeRequestWithAggs(request, { + sum: { + sum: { + field: TRANSACTION_DURATION, + }, + }, + }); + + const response = await setup.client.search(params); + + return arrayUnionToCallable( + response.aggregations?.transaction_groups.buckets ?? [] + ).map((bucket) => { + return { + key: bucket.key as BucketKey, + sum: bucket.sum.value, + }; + }); +} + +export async function getPercentiles({ request, setup }: MetricParams) { + const params = mergeRequestWithAggs(request, { + p95: { + percentiles: { + field: TRANSACTION_DURATION, + hdr: { number_of_significant_value_digits: 2 }, + percents: [95], + }, + }, + }); + + const response = await setup.client.search(params); + + return arrayUnionToCallable( + response.aggregations?.transaction_groups.buckets ?? [] + ).map((bucket) => { + return { + key: bucket.key as BucketKey, + p95: Object.values(bucket.p95.values)[0], + }; + }); +} diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/index.ts b/x-pack/plugins/apm/server/lib/transaction_groups/index.ts index 893e586b351a8..6e0d619268d44 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/index.ts @@ -10,19 +10,11 @@ import { SetupUIFilters, } from '../helpers/setup_request'; import { transactionGroupsFetcher, Options } from './fetcher'; -import { transactionGroupsTransformer } from './transform'; export async function getTransactionGroupList( options: Options, setup: Setup & SetupTimeRange & SetupUIFilters ) { - const { start, end } = setup; const bucketSize = setup.config['xpack.apm.ui.transactionGroupBucketSize']; - const response = await transactionGroupsFetcher(options, setup, bucketSize); - return transactionGroupsTransformer({ - response, - start, - end, - bucketSize, - }); + return await transactionGroupsFetcher(options, setup, bucketSize); } diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts b/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts index 2c5aa79bb3483..0b2ff3a72975b 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts @@ -31,7 +31,9 @@ describe('transaction group queries', () => { ) ); - expect(mock.params).toMatchSnapshot(); + const allParams = mock.spy.mock.calls.map((call) => call[0]); + + expect(allParams).toMatchSnapshot(); }); it('fetches top traces', async () => { @@ -46,6 +48,8 @@ describe('transaction group queries', () => { ) ); - expect(mock.params).toMatchSnapshot(); + const allParams = mock.spy.mock.calls.map((call) => call[0]); + + expect(allParams).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/transform.test.ts b/x-pack/plugins/apm/server/lib/transaction_groups/transform.test.ts deleted file mode 100644 index 0bb29e27f0219..0000000000000 --- a/x-pack/plugins/apm/server/lib/transaction_groups/transform.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ESResponse } from './fetcher'; -import { transactionGroupsResponse } from './mock_responses/transaction_groups_response'; -import { transactionGroupsTransformer } from './transform'; - -describe('transactionGroupsTransformer', () => { - it('should match snapshot', () => { - const { - bucketSize, - isAggregationAccurate, - items, - } = transactionGroupsTransformer({ - response: transactionGroupsResponse, - start: 100, - end: 2000, - bucketSize: 100, - }); - - expect(bucketSize).toBe(100); - expect(isAggregationAccurate).toBe(true); - expect(items).toMatchSnapshot(); - }); - - it('should transform response correctly', () => { - const bucket = { - key: { transaction: 'POST /api/orders' }, - doc_count: 180, - avg: { value: 255966.30555555556 }, - p95: { values: { '95.0': 320238.5 } }, - sum: { value: 3000000000 }, - sample: { - hits: { - total: 180, - hits: [{ _source: 'sample source' }], - }, - }, - }; - - const response = ({ - aggregations: { - transaction_groups: { - buckets: [bucket], - }, - }, - } as unknown) as ESResponse; - - expect( - transactionGroupsTransformer({ - response, - start: 100, - end: 20000, - bucketSize: 100, - }) - ).toEqual({ - bucketSize: 100, - isAggregationAccurate: true, - items: [ - { - averageResponseTime: 255966.30555555556, - impact: 0, - name: 'POST /api/orders', - p95: 320238.5, - sample: 'sample source', - transactionsPerMinute: 542.713567839196, - }, - ], - }); - }); - - it('`isAggregationAccurate` should be false if number of bucket is higher than `bucketSize`', () => { - const bucket = { - key: { transaction: 'POST /api/orders' }, - doc_count: 180, - avg: { value: 255966.30555555556 }, - p95: { values: { '95.0': 320238.5 } }, - sum: { value: 3000000000 }, - sample: { - hits: { - total: 180, - hits: [{ _source: 'sample source' }], - }, - }, - }; - - const response = ({ - aggregations: { - transaction_groups: { - buckets: [bucket, bucket, bucket, bucket], // four buckets returned - }, - }, - } as unknown) as ESResponse; - - const { isAggregationAccurate } = transactionGroupsTransformer({ - response, - start: 100, - end: 20000, - bucketSize: 3, // bucket size of three - }); - - expect(isAggregationAccurate).toEqual(false); - }); - - it('should calculate impact from sum', () => { - const getBucket = (sum: number) => ({ - key: { transaction: 'POST /api/orders' }, - doc_count: 180, - avg: { value: 300000 }, - p95: { values: { '95.0': 320000 } }, - sum: { value: sum }, - sample: { hits: { total: 180, hits: [{ _source: 'sample source' }] } }, - }); - - const response = ({ - aggregations: { - transaction_groups: { - buckets: [getBucket(10), getBucket(20), getBucket(50)], - }, - }, - } as unknown) as ESResponse; - - const { items } = transactionGroupsTransformer({ - response, - start: 100, - end: 20000, - bucketSize: 100, - }); - - expect(items.map((bucket) => bucket.impact)).toEqual([100, 25, 0]); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts b/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts deleted file mode 100644 index b04ff6764675d..0000000000000 --- a/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import moment from 'moment'; -import { orderBy } from 'lodash'; -import { ESResponse } from './fetcher'; - -function calculateRelativeImpacts(items: ITransactionGroup[]) { - const values = items - .map(({ impact }) => impact) - .filter((value) => value !== null) as number[]; - - const max = Math.max(...values); - const min = Math.min(...values); - - return items.map((bucket) => ({ - ...bucket, - impact: - bucket.impact !== null - ? ((bucket.impact - min) / (max - min)) * 100 || 0 - : 0, - })); -} - -const getBuckets = (response: ESResponse) => { - if (response.aggregations) { - return orderBy( - response.aggregations.transaction_groups.buckets, - ['sum.value'], - ['desc'] - ); - } - return []; -}; - -export type ITransactionGroup = ReturnType; -function getTransactionGroup( - bucket: ReturnType[0], - minutes: number -) { - const averageResponseTime = bucket.avg.value; - const transactionsPerMinute = bucket.doc_count / minutes; - const impact = bucket.sum.value; - const sample = bucket.sample.hits.hits[0]._source; - - return { - name: bucket.key.transaction, - sample, - p95: bucket.p95.values['95.0'], - averageResponseTime, - transactionsPerMinute, - impact, - }; -} - -export function transactionGroupsTransformer({ - response, - start, - end, - bucketSize, -}: { - response: ESResponse; - start: number; - end: number; - bucketSize: number; -}): { - items: ITransactionGroup[]; - isAggregationAccurate: boolean; - bucketSize: number; -} { - const buckets = getBuckets(response); - const duration = moment.duration(end - start); - const minutes = duration.asMinutes(); - const items = buckets.map((bucket) => getTransactionGroup(bucket, minutes)); - - const itemsWithRelativeImpact = calculateRelativeImpacts(items); - - return { - items: itemsWithRelativeImpact, - - // The aggregation is considered accurate if the configured bucket size is larger or equal to the number of buckets returned - // the actual number of buckets retrieved are `bucketsize + 1` to detect whether it's above the limit - isAggregationAccurate: bucketSize >= buckets.length, - bucketSize, - }; -} diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts index 1cecf14f2eeb8..e892284fd87cd 100644 --- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts +++ b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts @@ -35,7 +35,7 @@ export const getLocalFilterQuery = ({ }, }, } - : {}; + : null; return mergeProjection(projection, { body: { diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts index 588d5c7896db9..3833b93c8d1f7 100644 --- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts +++ b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts @@ -43,6 +43,7 @@ export async function getLocalUIFilters({ const response = await client.search(query); const filter = localUIFilters[name]; + const buckets = response?.aggregations?.by_terms?.buckets ?? []; return { diff --git a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts index ac7499c23e926..d25ec8709e3be 100644 --- a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts +++ b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Unionize } from 'utility-types'; +import { Unionize, UnionToIntersection } from 'utility-types'; type SortOrder = 'asc' | 'desc'; type SortInstruction = Record; @@ -288,10 +288,13 @@ interface AggregationResponsePart< } | undefined; composite: { - after_key: Record, number>; + after_key: Record< + GetCompositeKeys, + string | number + >; buckets: Array< { - key: Record, number>; + key: Record, string | number>; doc_count: number; } & BucketSubAggregationResponse< TAggregationOptionsMap['aggs'], @@ -337,6 +340,15 @@ interface AggregationResponsePart< // keyof AggregationResponsePart<{}, unknown> // >; +// ensures aggregations work with requests where aggregation options are a union type, +// e.g. { transaction_groups: { composite: any } | { terms: any } }. +// Union keys are not included in keyof. The type will fall back to keyof T if +// UnionToIntersection fails, which happens when there are conflicts between the union +// types, e.g. { foo: string; bar?: undefined } | { foo?: undefined; bar: string }; +export type ValidAggregationKeysOf< + T extends Record +> = keyof (UnionToIntersection extends never ? T : UnionToIntersection); + export type AggregationResponseMap< TAggregationInputMap extends AggregationInputMap | undefined, TDocument @@ -345,6 +357,6 @@ export type AggregationResponseMap< [TName in keyof TAggregationInputMap]: AggregationResponsePart< TAggregationInputMap[TName], TDocument - >[AggregationType & keyof TAggregationInputMap[TName]]; + >[AggregationType & ValidAggregationKeysOf]; } : undefined; diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/expectation/top_traces.expectation.json b/x-pack/test/apm_api_integration/basic/tests/traces/expectation/top_traces.expectation.json index bacb340292f93..4db040e92e7fa 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/expectation/top_traces.expectation.json +++ b/x-pack/test/apm_api_integration/basic/tests/traces/expectation/top_traces.expectation.json @@ -1,20 +1,35 @@ [ { - "name": "Process payment", + "key": { + "service.name": "opbeans-node", + "transaction.name": "Process payment" + }, + "averageResponseTime": 1745009, + "transactionsPerMinute": 0.25, + "impact": 100, "sample": { "@timestamp": "2020-06-29T06:48:29.892Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:39.379730Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:39.379730Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "observer": { "ephemeral_id": "99908b73-9813-4a73-baa6-993db405523a", @@ -34,59 +49,97 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "timestamp": { "us": 1593413309892019 }, - "trace": { "id": "bc393b659bef63291b6fa08e6f1d3f14" }, + "timestamp": { + "us": 1593413309892019 + }, + "trace": { + "id": "bc393b659bef63291b6fa08e6f1d3f14" + }, "transaction": { - "duration": { "us": 1745009 }, + "duration": { + "us": 1745009 + }, "id": "a58333df6d851cf1", "name": "Process payment", "result": "success", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "Worker" } - }, - "p95": 1744896, - "averageResponseTime": 1745009, - "transactionsPerMinute": 0.25, - "impact": 100 + } }, { - "name": "GET /api", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api" + }, + "averageResponseTime": 49816.15625, + "transactionsPerMinute": 8, + "impact": 91.32732325394932, "sample": { - "@timestamp": "2020-06-29T06:48:41.454Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:48:06.969Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.992834Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:08.306961Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -96,11 +149,18 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:41 GMT"], - "Transfer-Encoding": ["chunked"], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "0" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:06 GMT" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -130,61 +190,103 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413321454009 }, - "trace": { "id": "0507830eeff93f7bf1a354e4031097b3" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413286969018 + }, + "trace": { + "id": "87a828bcedd44d9e872d8f552fb04aa6" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 8334 }, - "id": "878250a8b937445d", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 25229 + }, + "id": "b1843afd04271423", "name": "GET /api", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { "domain": "opbeans-node", - "full": "http://opbeans-node:3000/api/products/6", - "original": "/api/products/6", - "path": "/api/products/6", + "full": "http://opbeans-node:3000/api/orders/474", + "original": "/api/orders/474", + "path": "/api/orders/474", "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 81888, - "averageResponseTime": 49816.15625, - "transactionsPerMinute": 8, - "impact": 91.32732325394932 + } }, { - "name": "/dashboard", + "key": { + "service.name": "client", + "transaction.name": "/dashboard" + }, + "averageResponseTime": 208000, + "transactionsPerMinute": 0.75, + "impact": 35.56882613781033, "sample": { - "@timestamp": "2020-06-29T06:48:21.621Z", - "agent": { "name": "rum-js", "version": "5.2.0" }, - "client": { "ip": "172.18.0.8" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:22.625275Z" }, + "@timestamp": "2020-06-29T06:48:07.275Z", + "agent": { + "name": "rum-js", + "version": "5.2.0" + }, + "client": { + "ip": "172.18.0.8" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:08.291261Z" + }, "http": { - "request": { "referrer": "" }, + "request": { + "referrer": "" + }, "response": { "decoded_body_size": 813, "encoded_body_size": 813, @@ -199,52 +301,73 @@ "version": "8.0.0", "version_major": 8 }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { - "language": { "name": "javascript" }, + "language": { + "name": "javascript" + }, "name": "client", "version": "1.0.0" }, - "source": { "ip": "172.18.0.8" }, - "timestamp": { "us": 1593413301621808 }, - "trace": { "id": "ee0ce8b38b8d5945829fc1c9432538bf" }, + "source": { + "ip": "172.18.0.8" + }, + "timestamp": { + "us": 1593413287275113 + }, + "trace": { + "id": "ca86ffcac7753ec8733933bd8fd45d11" + }, "transaction": { "custom": { "userConfig": { - "featureFlags": ["double-trouble", "4423-hotfix"], + "featureFlags": [ + "double-trouble", + "4423-hotfix" + ], "showDashboard": true } }, - "duration": { "us": 109000 }, - "id": "c546a6716b681bf2", + "duration": { + "us": 342000 + }, + "id": "c40f735132c8e864", "marks": { "agent": { - "domComplete": 98, - "domInteractive": 87, - "timeToFirstByte": 3 + "domComplete": 335, + "domInteractive": 327, + "timeToFirstByte": 16 }, "navigationTiming": { - "connectEnd": 0, - "connectStart": 0, - "domComplete": 98, - "domContentLoadedEventEnd": 87, - "domContentLoadedEventStart": 87, - "domInteractive": 87, - "domLoading": 8, - "domainLookupEnd": 0, - "domainLookupStart": 0, + "connectEnd": 12, + "connectStart": 12, + "domComplete": 335, + "domContentLoadedEventEnd": 327, + "domContentLoadedEventStart": 327, + "domInteractive": 327, + "domLoading": 21, + "domainLookupEnd": 12, + "domainLookupStart": 10, "fetchStart": 0, - "loadEventEnd": 98, - "loadEventStart": 98, - "requestStart": 1, - "responseEnd": 8, - "responseStart": 3 + "loadEventEnd": 335, + "loadEventStart": 335, + "requestStart": 12, + "responseEnd": 17, + "responseStart": 16 } }, "name": "/dashboard", - "page": { "referer": "", "url": "http://opbeans-node:3000/dashboard" }, + "page": { + "referer": "", + "url": "http://opbeans-node:3000/dashboard" + }, "sampled": true, - "span_count": { "started": 8 }, + "span_count": { + "started": 9 + }, "type": "page-load" }, "url": { @@ -261,50 +384,75 @@ "name": "arthurdent" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "HeadlessChrome", "original": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36", - "os": { "name": "Linux" }, + "os": { + "name": "Linux" + }, "version": "79.0.3945" } - }, - "p95": 341504, - "averageResponseTime": 208000, - "transactionsPerMinute": 0.75, - "impact": 35.56882613781033 + } }, { - "name": "DispatcherServlet#doGet", + "key": { + "service.name": "opbeans-java", + "transaction.name": "DispatcherServlet#doGet" + }, + "averageResponseTime": 36010.53846153846, + "transactionsPerMinute": 3.25, + "impact": 26.61043592713186, "sample": { - "@timestamp": "2020-06-29T06:48:40.104Z", + "@timestamp": "2020-06-29T06:48:10.529Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:46.706956Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:15.757591Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, @@ -321,77 +469,132 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Servlet API" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Servlet API" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413320104008 }, - "trace": { "id": "90bd7780b32cc51a7f4c200b1e0c170f" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413290529006 + }, + "trace": { + "id": "66e3db4cf016b138a43d319d15174891" + }, "transaction": { - "duration": { "us": 8896 }, - "id": "40b22b21e92bbb20", + "duration": { + "us": 34366 + }, + "id": "7ea720a0175e7ffa", "name": "DispatcherServlet#doGet", "result": "HTTP 2xx", "sampled": true, - "span_count": { "dropped": 0, "started": 1 }, + "span_count": { + "dropped": 0, + "started": 1 + }, "type": "request" }, "url": { "domain": "172.18.0.6", - "full": "http://172.18.0.6:3000/api/orders", - "path": "/api/orders", + "full": "http://172.18.0.6:3000/api/products", + "path": "/api/products", "port": 3000, "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 34528, - "averageResponseTime": 36010.53846153846, - "transactionsPerMinute": 3.25, - "impact": 26.61043592713186 + } }, { - "name": "POST /api/orders", + "key": { + "service.name": "opbeans-node", + "transaction.name": "POST /api/orders" + }, + "averageResponseTime": 270684, + "transactionsPerMinute": 0.25, + "impact": 15.261616628971955, "sample": { "@timestamp": "2020-06-29T06:48:39.953Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.991549Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:43.991549Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { - "body": { "original": "[REDACTED]" }, + "body": { + "original": "[REDACTED]" + }, "headers": { - "Accept": ["application/json"], - "Connection": ["close"], - "Content-Length": ["129"], - "Content-Type": ["application/json"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Accept": [ + "application/json" + ], + "Connection": [ + "close" + ], + "Content-Length": [ + "129" + ], + "Content-Type": [ + "application/json" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "post", "socket": { @@ -401,12 +604,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["13"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:40 GMT"], - "Etag": ["W/\"d-eEOWU4Cnr5DZ23ErRUeYu9oOIks\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "13" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:40 GMT" + ], + "Etag": [ + "W/\"d-eEOWU4Cnr5DZ23ErRUeYu9oOIks\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -436,29 +651,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413319953033 }, - "trace": { "id": "52b8fda5f6df745b990740ba18378620" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413319953033 + }, + "trace": { + "id": "52b8fda5f6df745b990740ba18378620" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 270684 }, + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 270684 + }, "id": "a3afc2a112e9c893", "name": "POST /api/orders", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 16 }, + "span_count": { + "started": 16 + }, "type": "request" }, "url": { @@ -469,50 +707,77 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 270336, - "averageResponseTime": 270684, - "transactionsPerMinute": 0.25, - "impact": 15.261616628971955 + } }, { - "name": "ResourceHttpRequestHandler", + "key": { + "service.name": "opbeans-java", + "transaction.name": "ResourceHttpRequestHandler" + }, + "averageResponseTime": 14419.42857142857, + "transactionsPerMinute": 3.5, + "impact": 11.30657439844125, "sample": { - "@timestamp": "2020-06-29T06:48:44.376Z", + "@timestamp": "2020-06-29T06:48:06.640Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:46.720380Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:15.517678Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, @@ -529,63 +794,100 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Spring Web MVC", "version": "5.0.6.RELEASE" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Spring Web MVC", + "version": "5.0.6.RELEASE" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413324376010 }, - "trace": { "id": "7e70dc471913473e7d3bffda9b27d720" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413286640008 + }, + "trace": { + "id": "81d8ffb0a39e755eed400f6486e15672" + }, "transaction": { - "duration": { "us": 1420 }, - "id": "5c4e9f4b0846c2f8", + "duration": { + "us": 2953 + }, + "id": "353d42a2f9046e99", "name": "ResourceHttpRequestHandler", "result": "HTTP 4xx", "sampled": true, - "span_count": { "dropped": 0, "started": 0 }, + "span_count": { + "dropped": 0, + "started": 0 + }, "type": "request" }, "url": { "domain": "172.18.0.6", - "full": "http://172.18.0.6:3000/api/types", - "path": "/api/types", + "full": "http://172.18.0.6:3000/api/types/3", + "path": "/api/types/3", "port": 3000, "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 4120, - "averageResponseTime": 14419.42857142857, - "transactionsPerMinute": 3.5, - "impact": 11.30657439844125 + } }, { - "name": "/orders", + "key": { + "service.name": "client", + "transaction.name": "/orders" + }, + "averageResponseTime": 81500, + "transactionsPerMinute": 0.5, + "impact": 9.072365225837785, "sample": { - "@timestamp": "2020-06-29T06:48:38.358Z", - "agent": { "name": "rum-js", "version": "5.2.0" }, - "client": { "ip": "172.18.0.8" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:39.365914Z" }, + "@timestamp": "2020-06-29T06:48:29.296Z", + "agent": { + "name": "rum-js", + "version": "5.2.0" + }, + "client": { + "ip": "172.18.0.8" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:29.986555Z" + }, "http": { - "request": { "referrer": "" }, - "response": { - "decoded_body_size": 813, - "encoded_body_size": 813, - "transfer_size": 962 + "request": { + "referrer": "" } }, "observer": { @@ -596,53 +898,50 @@ "version": "8.0.0", "version_major": 8 }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { - "language": { "name": "javascript" }, + "language": { + "name": "javascript" + }, "name": "client", "version": "1.0.0" }, - "source": { "ip": "172.18.0.8" }, - "timestamp": { "us": 1593413318358392 }, - "trace": { "id": "c1dea08a4128e776fd9965ccf8c8da99" }, + "source": { + "ip": "172.18.0.8" + }, + "timestamp": { + "us": 1593413309296660 + }, + "trace": { + "id": "978b56807e0b7a27cbc41a0dfb665f47" + }, "transaction": { "custom": { "userConfig": { - "featureFlags": ["double-trouble", "4423-hotfix"], + "featureFlags": [ + "double-trouble", + "4423-hotfix" + ], "showDashboard": true } }, - "duration": { "us": 140000 }, - "id": "4f2ea2796645d6e5", - "marks": { - "agent": { - "domComplete": 126, - "domInteractive": 116, - "timeToFirstByte": 3 - }, - "navigationTiming": { - "connectEnd": 0, - "connectStart": 0, - "domComplete": 126, - "domContentLoadedEventEnd": 116, - "domContentLoadedEventStart": 116, - "domInteractive": 116, - "domLoading": 20, - "domainLookupEnd": 0, - "domainLookupStart": 0, - "fetchStart": 0, - "loadEventEnd": 126, - "loadEventStart": 126, - "requestStart": 1, - "responseEnd": 3, - "responseStart": 3 - } + "duration": { + "us": 23000 }, + "id": "c3801eadbdef5c7c", "name": "/orders", - "page": { "referer": "", "url": "http://opbeans-node:3000/orders" }, + "page": { + "referer": "", + "url": "http://opbeans-node:3000/orders" + }, "sampled": true, - "span_count": { "started": 7 }, - "type": "page-load" + "span_count": { + "started": 1 + }, + "type": "route-change" }, "url": { "domain": "opbeans-node", @@ -652,59 +951,94 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "adastra@example.com", "id": "3", "name": "trillian" }, + "user": { + "email": "arthur.dent@example.com", + "id": "1", + "name": "arthurdent" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "HeadlessChrome", "original": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36", - "os": { "name": "Linux" }, + "os": { + "name": "Linux" + }, "version": "79.0.3945" } - }, - "p95": 140160, - "averageResponseTime": 81500, - "transactionsPerMinute": 0.5, - "impact": 9.072365225837785 + } }, { - "name": "APIRestController#customer", + "key": { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#customer" + }, + "averageResponseTime": 19370.6, + "transactionsPerMinute": 1.25, + "impact": 5.270496679320978, "sample": { - "@timestamp": "2020-06-29T06:48:38.893Z", + "@timestamp": "2020-06-29T06:48:08.631Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:46.680126Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:15.536897Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, "headers": { - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:38 GMT"], - "Transfer-Encoding": ["chunked"] + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:08 GMT" + ], + "Transfer-Encoding": [ + "chunked" + ] }, "headers_sent": true, "status_code": 200 @@ -719,59 +1053,101 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Spring Web MVC", "version": "5.0.6.RELEASE" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Spring Web MVC", + "version": "5.0.6.RELEASE" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413318893006 }, - "trace": { "id": "efcf3446b51d080dbde1339969cf79a0" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413288631008 + }, + "trace": { + "id": "c00da24c5c793cd679ce3df47cee8f37" + }, "transaction": { - "duration": { "us": 4594 }, - "id": "31ef64d71933e846", + "duration": { + "us": 76826 + }, + "id": "3c8403055ff75866", "name": "APIRestController#customer", "result": "HTTP 2xx", "sampled": true, - "span_count": { "dropped": 0, "started": 2 }, + "span_count": { + "dropped": 0, + "started": 2 + }, "type": "request" }, "url": { "domain": "172.18.0.6", - "full": "http://172.18.0.6:3000/api/customers/235", - "path": "/api/customers/235", + "full": "http://172.18.0.6:3000/api/customers/56", + "path": "/api/customers/56", "port": 3000, "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 77280, - "averageResponseTime": 19370.6, - "transactionsPerMinute": 1.25, - "impact": 5.270496679320978 + } }, { - "name": "/products", + "key": { + "service.name": "client", + "transaction.name": "/products" + }, + "averageResponseTime": 77000, + "transactionsPerMinute": 0.25, + "impact": 4.129424578484989, "sample": { "@timestamp": "2020-06-29T06:48:48.824Z", - "agent": { "name": "rum-js", "version": "5.2.0" }, - "client": { "ip": "172.18.0.8" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:49.293664Z" }, + "agent": { + "name": "rum-js", + "version": "5.2.0" + }, + "client": { + "ip": "172.18.0.8" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:49.293664Z" + }, "http": { - "request": { "referrer": "" }, + "request": { + "referrer": "" + }, "response": { "decoded_body_size": 813, "encoded_body_size": 813, @@ -786,23 +1162,39 @@ "version": "8.0.0", "version_major": 8 }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { - "language": { "name": "javascript" }, + "language": { + "name": "javascript" + }, "name": "client", "version": "1.0.0" }, - "source": { "ip": "172.18.0.8" }, - "timestamp": { "us": 1593413328824656 }, - "trace": { "id": "f6c4a9197bbd080bd45072970f251525" }, + "source": { + "ip": "172.18.0.8" + }, + "timestamp": { + "us": 1593413328824656 + }, + "trace": { + "id": "f6c4a9197bbd080bd45072970f251525" + }, "transaction": { "custom": { "userConfig": { - "featureFlags": ["double-trouble", "4423-hotfix"], + "featureFlags": [ + "double-trouble", + "4423-hotfix" + ], "showDashboard": true } }, - "duration": { "us": 77000 }, + "duration": { + "us": 77000 + }, "id": "a11ede1968973bc5", "marks": { "agent": { @@ -829,9 +1221,14 @@ } }, "name": "/products", - "page": { "referer": "", "url": "http://opbeans-node:3000/products" }, + "page": { + "referer": "", + "url": "http://opbeans-node:3000/products" + }, "sampled": true, - "span_count": { "started": 5 }, + "span_count": { + "started": 5 + }, "type": "page-load" }, "url": { @@ -842,29 +1239,52 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "z@example.com", "id": "4", "name": "zaphod" }, + "user": { + "email": "z@example.com", + "id": "4", + "name": "zaphod" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "HeadlessChrome", "original": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36", - "os": { "name": "Linux" }, + "os": { + "name": "Linux" + }, "version": "79.0.3945" } - }, - "p95": 76800, - "averageResponseTime": 77000, - "transactionsPerMinute": 0.25, - "impact": 4.129424578484989 + } }, { - "name": "/customers", + "key": { + "service.name": "client", + "transaction.name": "/customers" + }, + "averageResponseTime": 33500, + "transactionsPerMinute": 0.5, + "impact": 3.5546640380951287, "sample": { - "@timestamp": "2020-06-29T06:48:48.287Z", - "agent": { "name": "rum-js", "version": "5.2.0" }, - "client": { "ip": "172.18.0.8" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:49.292535Z" }, - "http": { "request": { "referrer": "" } }, + "@timestamp": "2020-06-29T06:48:35.071Z", + "agent": { + "name": "rum-js", + "version": "5.2.0" + }, + "client": { + "ip": "172.18.0.8" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:36.077184Z" + }, + "http": { + "request": { + "referrer": "" + } + }, "observer": { "ephemeral_id": "99908b73-9813-4a73-baa6-993db405523a", "hostname": "aa0bd613aa4c", @@ -873,28 +1293,49 @@ "version": "8.0.0", "version_major": 8 }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { - "language": { "name": "javascript" }, + "language": { + "name": "javascript" + }, "name": "client", "version": "1.0.0" }, - "source": { "ip": "172.18.0.8" }, - "timestamp": { "us": 1593413328287946 }, - "trace": { "id": "48d130530a1fc0b2b99d138d3461bad4" }, + "source": { + "ip": "172.18.0.8" + }, + "timestamp": { + "us": 1593413315071116 + }, + "trace": { + "id": "547a92e82a25387321d1b967f2dd0f48" + }, "transaction": { "custom": { "userConfig": { - "featureFlags": ["double-trouble", "4423-hotfix"], + "featureFlags": [ + "double-trouble", + "4423-hotfix" + ], "showDashboard": true } }, - "duration": { "us": 39000 }, - "id": "2f3a2b0fd3016d3e", + "duration": { + "us": 28000 + }, + "id": "d24f9b9dacb83450", "name": "/customers", - "page": { "referer": "", "url": "http://opbeans-node:3000/customers" }, + "page": { + "referer": "", + "url": "http://opbeans-node:3000/customers" + }, "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "route-change" }, "url": { @@ -905,59 +1346,94 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "adastra@example.com", "id": "3", "name": "trillian" }, + "user": { + "email": "arthur.dent@example.com", + "id": "1", + "name": "arthurdent" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "HeadlessChrome", "original": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36", - "os": { "name": "Linux" }, + "os": { + "name": "Linux" + }, "version": "79.0.3945" } - }, - "p95": 39040, - "averageResponseTime": 33500, - "transactionsPerMinute": 0.5, - "impact": 3.5546640380951287 + } }, { - "name": "APIRestController#customers", + "key": { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#customers" + }, + "averageResponseTime": 16690.75, + "transactionsPerMinute": 1, + "impact": 3.541042213287889, "sample": { - "@timestamp": "2020-06-29T06:48:43.765Z", + "@timestamp": "2020-06-29T06:48:22.372Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:46.716850Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:25.888154Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, "headers": { - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:43 GMT"], - "Transfer-Encoding": ["chunked"] + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:21 GMT" + ], + "Transfer-Encoding": [ + "chunked" + ] }, "headers_sent": true, "status_code": 500 @@ -972,29 +1448,56 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Spring Web MVC", "version": "5.0.6.RELEASE" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Spring Web MVC", + "version": "5.0.6.RELEASE" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413323765008 }, - "trace": { "id": "affce4cea9b60bd5b635dc1bd2e4ce79" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413302372009 + }, + "trace": { + "id": "21dd795dc3a260b1bf7ebbbac1e86fb8" + }, "transaction": { - "duration": { "us": 10880 }, - "id": "cfe0a84b49b4a340", + "duration": { + "us": 14795 + }, + "id": "0157fc513282138f", "name": "APIRestController#customers", "result": "HTTP 5xx", "sampled": true, - "span_count": { "dropped": 0, "started": 2 }, + "span_count": { + "dropped": 0, + "started": 2 + }, "type": "request" }, "url": { @@ -1005,40 +1508,61 @@ "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 26432, - "averageResponseTime": 16690.75, - "transactionsPerMinute": 1, - "impact": 3.541042213287889 + } }, { - "name": "GET /log-message", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /log-message" + }, + "averageResponseTime": 32667.5, + "transactionsPerMinute": 0.5, + "impact": 3.458966408120217, "sample": { - "@timestamp": "2020-06-29T06:48:28.944Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:48:25.944Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:39.370695Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:29.976822Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -1048,12 +1572,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["24"], - "Content-Type": ["text/html; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:28 GMT"], - "Etag": ["W/\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "24" + ], + "Content-Type": [ + "text/html; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:25 GMT" + ], + "Etag": [ + "W/\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 500 }, @@ -1083,29 +1619,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413308944016 }, - "trace": { "id": "afabe4cb397616f5ec35a2f3da49ba62" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413305944023 + }, + "trace": { + "id": "cd2ad726ad164d701c5d3103cbab0c81" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 26788 }, - "id": "cc8c4261387507cf", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 38547 + }, + "id": "9e41667eb64dea55", "name": "GET /log-message", "result": "HTTP 5xx", "sampled": true, - "span_count": { "started": 0 }, + "span_count": { + "started": 0 + }, "type": "request" }, "url": { @@ -1116,60 +1675,82 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 38528, - "averageResponseTime": 32667.5, - "transactionsPerMinute": 0.5, - "impact": 3.458966408120217 + } }, { - "name": "APIRestController#stats", + "key": { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#stats" + }, + "averageResponseTime": 15535, + "transactionsPerMinute": 1, + "impact": 3.275330415465657, "sample": { - "@timestamp": "2020-06-29T06:48:42.549Z", + "@timestamp": "2020-06-29T06:48:09.912Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:46.715898Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:15.543824Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, - "headers": { - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:42 GMT"], - "Transfer-Encoding": ["chunked"] - }, "headers_sent": true, - "status_code": 200 + "status_code": 500 }, "version": "1.1" }, @@ -1181,29 +1762,56 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Spring Web MVC", "version": "5.0.6.RELEASE" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Spring Web MVC", + "version": "5.0.6.RELEASE" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413322549008 }, - "trace": { "id": "c3556e143784f94d4b4220dd40687fae" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413289912007 + }, + "trace": { + "id": "a17ceae4e18d50430ca15ecca5a3e69f" + }, "transaction": { - "duration": { "us": 9166 }, - "id": "ac40e567f63c3eef", + "duration": { + "us": 10930 + }, + "id": "9fb330060bb73271", "name": "APIRestController#stats", - "result": "HTTP 2xx", + "result": "HTTP 5xx", "sampled": true, - "span_count": { "dropped": 0, "started": 5 }, + "span_count": { + "dropped": 0, + "started": 5 + }, "type": "request" }, "url": { @@ -1214,40 +1822,61 @@ "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 32064, - "averageResponseTime": 15535, - "transactionsPerMinute": 1, - "impact": 3.275330415465657 + } }, { - "name": "GET /api/customers", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api/customers" + }, + "averageResponseTime": 20092, + "transactionsPerMinute": 0.75, + "impact": 3.168195050736987, "sample": { - "@timestamp": "2020-06-29T06:48:37.952Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:48:28.444Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:39.492402Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:29.982737Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -1257,12 +1886,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["186769"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:37 GMT"], - "Etag": ["W/\"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "186769" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:28 GMT" + ], + "Etag": [ + "W/\"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -1292,29 +1933,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413317952016 }, - "trace": { "id": "5d99327edae38ed735e8d7a6028cf719" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413308444015 + }, + "trace": { + "id": "792fb0b00256164e88b277ec40b65e14" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 16824 }, - "id": "071808429ec9d00b", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 26471 + }, + "id": "6c1f848752563d2b", "name": "GET /api/customers", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "request" }, "url": { @@ -1325,42 +1989,67 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 26368, - "averageResponseTime": 20092, - "transactionsPerMinute": 0.75, - "impact": 3.168195050736987 + } }, { - "name": "GET /api/products/:id", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api/products/:id" + }, + "averageResponseTime": 13516.5, + "transactionsPerMinute": 1, + "impact": 2.8112687551548836, "sample": { - "@timestamp": "2020-06-29T06:48:24.977Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:47:57.555Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:27.004717Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:47:59.085077Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -1370,12 +2059,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["231"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:24 GMT"], - "Etag": ["W/\"e7-kkuzj37GZDzXDh0CWqh5Gan0VO4\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "231" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:47:57 GMT" + ], + "Etag": [ + "W/\"e7-6JlJegaJ+ir0C8I8EmmOjms1dnc\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -1401,79 +2102,127 @@ "/usr/local/lib/node_modules/pm2/lib/ProcessContainer.js", "ecosystem-workload.config.js" ], - "pid": 137, + "pid": 87, "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413304977014 }, - "trace": { "id": "b9b290bca14c99962fa9a1c509401630" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413277555176 + }, + "trace": { + "id": "8365e1763f19e4067b88521d4d9247a0" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 4482 }, - "id": "b8d8284ff1fc25d6", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 37709 + }, + "id": "be2722a418272f10", "name": "GET /api/products/:id", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { "domain": "opbeans-node", - "full": "http://opbeans-node:3000/api/products/3", - "original": "/api/products/3", - "path": "/api/products/3", + "full": "http://opbeans-node:3000/api/products/1", + "original": "/api/products/1", + "path": "/api/products/1", "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 37856, - "averageResponseTime": 13516.5, - "transactionsPerMinute": 1, - "impact": 2.8112687551548836 + } }, { - "name": "GET /api/types", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api/types" + }, + "averageResponseTime": 26992.5, + "transactionsPerMinute": 0.5, + "impact": 2.8066131947777255, "sample": { - "@timestamp": "2020-06-29T06:48:26.443Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:47:52.935Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:29.977518Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:47:55.471071Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -1483,12 +2232,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["112"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:26 GMT"], - "Etag": ["W/\"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "112" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:47:52 GMT" + ], + "Etag": [ + "W/\"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -1514,33 +2275,56 @@ "/usr/local/lib/node_modules/pm2/lib/ProcessContainer.js", "ecosystem-workload.config.js" ], - "pid": 137, + "pid": 63, "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413306443014 }, - "trace": { "id": "be3f4eb50d253afc032c90eacaa85072" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413272935117 + }, + "trace": { + "id": "2946c536a33d163d0c984d00d1f3839a" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 8892 }, - "id": "ccce129bb8c6b125", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 45093 + }, + "id": "103482fda88b9400", "name": "GET /api/types", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "request" }, "url": { @@ -1551,42 +2335,67 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 45248, - "averageResponseTime": 26992.5, - "transactionsPerMinute": 0.5, - "impact": 2.8066131947777255 + } }, { - "name": "GET static file", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET static file" + }, + "averageResponseTime": 3492.9285714285716, + "transactionsPerMinute": 3.5, + "impact": 2.5144049360435208, "sample": { - "@timestamp": "2020-06-29T06:48:40.953Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:47:53.427Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.992454Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:47:55.472070Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -1596,15 +2405,33 @@ }, "response": { "headers": { - "Accept-Ranges": ["bytes"], - "Cache-Control": ["public, max-age=0"], - "Connection": ["close"], - "Content-Length": ["15086"], - "Content-Type": ["image/x-icon"], - "Date": ["Mon, 29 Jun 2020 06:48:40 GMT"], - "Etag": ["W/\"3aee-1725aff14f0\""], - "Last-Modified": ["Thu, 28 May 2020 11:16:06 GMT"], - "X-Powered-By": ["Express"] + "Accept-Ranges": [ + "bytes" + ], + "Cache-Control": [ + "public, max-age=0" + ], + "Connection": [ + "close" + ], + "Content-Length": [ + "15086" + ], + "Content-Type": [ + "image/x-icon" + ], + "Date": [ + "Mon, 29 Jun 2020 06:47:53 GMT" + ], + "Etag": [ + "W/\"3aee-1725aff14f0\"" + ], + "Last-Modified": [ + "Thu, 28 May 2020 11:16:06 GMT" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -1624,32 +2451,53 @@ "/usr/local/lib/node_modules/pm2/lib/ProcessContainer.js", "ecosystem-workload.config.js" ], - "pid": 137, + "pid": 63, "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413320953016 }, - "trace": { "id": "292393440bbe04385f3c2e3715ac35fe" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413273427016 + }, + "trace": { + "id": "ec8a804fedf28fcf81d5682d69a16970" + }, "transaction": { - "duration": { "us": 1671 }, - "id": "d1d964ca1865dce3", + "duration": { + "us": 4934 + }, + "id": "ab90a62901b770e6", "name": "GET static file", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 0 }, + "span_count": { + "started": 0 + }, "type": "request" }, "url": { @@ -1661,55 +2509,84 @@ "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 11900, - "averageResponseTime": 3492.9285714285716, - "transactionsPerMinute": 3.5, - "impact": 2.5144049360435208 + } }, { - "name": "APIRestController#customerWhoBought", + "key": { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#customerWhoBought" + }, + "averageResponseTime": 3742.153846153846, + "transactionsPerMinute": 3.25, + "impact": 2.4998634943716573, "sample": { - "@timestamp": "2020-06-29T06:48:44.982Z", + "@timestamp": "2020-06-29T06:48:11.166Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:46.721114Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:15.763228Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, "headers": { - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:44 GMT"], - "Transfer-Encoding": ["chunked"] + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:10 GMT" + ], + "Transfer-Encoding": [ + "chunked" + ] }, "headers_sent": true, "status_code": 200 @@ -1724,73 +2601,121 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Spring Web MVC", "version": "5.0.6.RELEASE" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Spring Web MVC", + "version": "5.0.6.RELEASE" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413324982008 }, - "trace": { "id": "e5ce8ba877f69510e7abc3f0d11c1e4f" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413291166005 + }, + "trace": { + "id": "fa0d353eb7967b344ed37674f40b2884" + }, "transaction": { - "duration": { "us": 2797 }, - "id": "b8c1bd3b31b197d3", + "duration": { + "us": 4453 + }, + "id": "bce4ce4b09ded6ca", "name": "APIRestController#customerWhoBought", "result": "HTTP 2xx", "sampled": true, - "span_count": { "dropped": 0, "started": 1 }, + "span_count": { + "dropped": 0, + "started": 2 + }, "type": "request" }, "url": { "domain": "172.18.0.6", - "full": "http://172.18.0.6:3000/api/products/5/customers", - "path": "/api/products/5/customers", + "full": "http://172.18.0.6:3000/api/products/3/customers", + "path": "/api/products/3/customers", "port": 3000, "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 4464, - "averageResponseTime": 3742.153846153846, - "transactionsPerMinute": 3.25, - "impact": 2.4998634943716573 + } }, { - "name": "GET /log-error", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /log-error" + }, + "averageResponseTime": 35846, + "transactionsPerMinute": 0.25, + "impact": 1.7640550505645587, "sample": { "@timestamp": "2020-06-29T06:48:07.467Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:18.533253Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:18.533253Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -1800,12 +2725,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["24"], - "Content-Type": ["text/html; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:07 GMT"], - "Etag": ["W/\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "24" + ], + "Content-Type": [ + "text/html; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:07 GMT" + ], + "Etag": [ + "W/\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 500 }, @@ -1835,29 +2772,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413287467017 }, - "trace": { "id": "d518b2c4d72cd2aaf1e39bad7ebcbdbb" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413287467017 + }, + "trace": { + "id": "d518b2c4d72cd2aaf1e39bad7ebcbdbb" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 35846 }, + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 35846 + }, "id": "c7a30c1b076907ec", "name": "GET /log-error", "result": "HTTP 5xx", "sampled": true, - "span_count": { "started": 0 }, + "span_count": { + "started": 0 + }, "type": "request" }, "url": { @@ -1868,57 +2828,90 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 35840, - "averageResponseTime": 35846, - "transactionsPerMinute": 0.25, - "impact": 1.7640550505645587 + } }, { - "name": "APIRestController#topProducts", + "key": { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#topProducts" + }, + "averageResponseTime": 4825, + "transactionsPerMinute": 1.75, + "impact": 1.6450221426498186, "sample": { - "@timestamp": "2020-06-29T06:48:45.587Z", + "@timestamp": "2020-06-29T06:48:11.778Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:46.770758Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:15.764351Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, "headers": { - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:44 GMT"], - "Transfer-Encoding": ["chunked"] + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:11 GMT" + ], + "Transfer-Encoding": [ + "chunked" + ] }, "headers_sent": true, "status_code": 200 @@ -1933,29 +2926,56 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Spring Web MVC", "version": "5.0.6.RELEASE" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Spring Web MVC", + "version": "5.0.6.RELEASE" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413325587007 }, - "trace": { "id": "4470d0cc076e22018e2dd258a25c8812" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413291778008 + }, + "trace": { + "id": "d65e9816f1f6db3961867f7b6d1d4e6a" + }, "transaction": { - "duration": { "us": 4070 }, - "id": "cb860b712121d0d8", + "duration": { + "us": 4168 + }, + "id": "a72f4bb8149ecdc5", "name": "APIRestController#topProducts", "result": "HTTP 2xx", "sampled": true, - "span_count": { "dropped": 0, "started": 1 }, + "span_count": { + "dropped": 0, + "started": 2 + }, "type": "request" }, "url": { @@ -1966,40 +2986,61 @@ "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 7344, - "averageResponseTime": 4825, - "transactionsPerMinute": 1.75, - "impact": 1.6450221426498186 + } }, { - "name": "GET /api/products/top", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api/products/top" + }, + "averageResponseTime": 33097, + "transactionsPerMinute": 0.25, + "impact": 1.6060533780113861, "sample": { "@timestamp": "2020-06-29T06:48:01.200Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:02.734903Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:02.734903Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -2009,12 +3050,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["2"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:01 GMT"], - "Etag": ["W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "2" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:01 GMT" + ], + "Etag": [ + "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -2044,29 +3097,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413281200133 }, - "trace": { "id": "195f32efeb6f91e2f71b6bc8bb74ae3a" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413281200133 + }, + "trace": { + "id": "195f32efeb6f91e2f71b6bc8bb74ae3a" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 33097 }, + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 33097 + }, "id": "22e72956dfc8967a", "name": "GET /api/products/top", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { @@ -2077,42 +3153,67 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 33024, - "averageResponseTime": 33097, - "transactionsPerMinute": 0.25, - "impact": 1.6060533780113861 + } }, { - "name": "GET /api/products", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api/products" + }, + "averageResponseTime": 6583, + "transactionsPerMinute": 1, + "impact": 1.2172278724376455, "sample": { - "@timestamp": "2020-06-29T06:48:23.477Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:48:21.475Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:27.001228Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:26.996210Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -2122,12 +3223,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["1023"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:23 GMT"], - "Etag": ["W/\"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "1023" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:21 GMT" + ], + "Etag": [ + "W/\"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -2157,29 +3270,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413303477028 }, - "trace": { "id": "9f26158b9a3915577b3cccc17636ea01" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413301475015 + }, + "trace": { + "id": "389b26b16949c7f783223de4f14b788c" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 7150 }, - "id": "27ff4add22ac2e1b", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 6775 + }, + "id": "d2d4088a0b104fb4", "name": "GET /api/products", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "request" }, "url": { @@ -2190,46 +3326,79 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 8160, - "averageResponseTime": 6583, - "transactionsPerMinute": 1, - "impact": 1.2172278724376455 + } }, { - "name": "POST /api", + "key": { + "service.name": "opbeans-node", + "transaction.name": "POST /api" + }, + "averageResponseTime": 20011, + "transactionsPerMinute": 0.25, + "impact": 0.853921734857215, "sample": { "@timestamp": "2020-06-29T06:48:25.478Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:27.005671Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:27.005671Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { - "body": { "original": "[REDACTED]" }, + "body": { + "original": "[REDACTED]" + }, "headers": { - "Accept": ["application/json"], - "Connection": ["close"], - "Content-Length": ["129"], - "Content-Type": ["application/json"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Accept": [ + "application/json" + ], + "Connection": [ + "close" + ], + "Content-Length": [ + "129" + ], + "Content-Type": [ + "application/json" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "post", "socket": { @@ -2239,12 +3408,24 @@ }, "response": { "headers": { - "Allow": ["GET"], - "Connection": ["close"], - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:25 GMT"], - "Transfer-Encoding": ["chunked"], - "X-Powered-By": ["Express"] + "Allow": [ + "GET" + ], + "Connection": [ + "close" + ], + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:25 GMT" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 405 }, @@ -2274,29 +3455,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413305478010 }, - "trace": { "id": "4bd9027dd1e355ec742970e2d6333124" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413305478010 + }, + "trace": { + "id": "4bd9027dd1e355ec742970e2d6333124" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 20011 }, + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 20011 + }, "id": "94104435cf151478", "name": "POST /api", "result": "HTTP 4xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { @@ -2307,42 +3511,67 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 19968, - "averageResponseTime": 20011, - "transactionsPerMinute": 0.25, - "impact": 0.853921734857215 + } }, { - "name": "GET /api/types/:id", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api/types/:id" + }, + "averageResponseTime": 8181, + "transactionsPerMinute": 0.5, + "impact": 0.6441916136689552, "sample": { - "@timestamp": "2020-06-29T06:48:12.972Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:47:53.928Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:18.543436Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:47:55.472718Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -2352,12 +3581,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["205"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:12 GMT"], - "Etag": ["W/\"cd-pFMi1QOVY6YqWe+nwcbZVviCths\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "205" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:47:53 GMT" + ], + "Etag": [ + "W/\"cd-pFMi1QOVY6YqWe+nwcbZVviCths\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -2383,33 +3624,56 @@ "/usr/local/lib/node_modules/pm2/lib/ProcessContainer.js", "ecosystem-workload.config.js" ], - "pid": 137, + "pid": 63, "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413292972011 }, - "trace": { "id": "aea65cef5f902dda5d8e38f9fb38864d" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413273928016 + }, + "trace": { + "id": "0becaafb422bfeb69e047bf7153aa469" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 6300 }, - "id": "a5bdbe43ac05fae2", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 10062 + }, + "id": "0cee4574091bda3b", "name": "GET /api/types/:id", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "request" }, "url": { @@ -2420,42 +3684,67 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 10080, - "averageResponseTime": 8181, - "transactionsPerMinute": 0.5, - "impact": 0.6441916136689552 + } }, { - "name": "GET /api/stats", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api/stats" + }, + "averageResponseTime": 5098, + "transactionsPerMinute": 0.75, + "impact": 0.582807187955318, "sample": { - "@timestamp": "2020-06-29T06:48:39.451Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:48:34.949Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.984824Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:39.479316Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -2465,12 +3754,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["92"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:39 GMT"], - "Etag": ["W/\"5c-6I+bqIiLxvkWuwBUnTxhBoK4lBk\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "92" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:34 GMT" + ], + "Etag": [ + "W/\"5c-6I+bqIiLxvkWuwBUnTxhBoK4lBk\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -2500,29 +3801,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413319451016 }, - "trace": { "id": "a05787cb03a0af0863fab5e05de942f1" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413314949017 + }, + "trace": { + "id": "616b3b77abd5534c61d6c0438469aee2" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 5050 }, - "id": "a7e004eba8f021ce", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 5459 + }, + "id": "5b4971de59d2099d", "name": "GET /api/stats", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 4 }, + "span_count": { + "started": 4 + }, "type": "request" }, "url": { @@ -2533,42 +3857,67 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 5440, - "averageResponseTime": 5098, - "transactionsPerMinute": 0.75, - "impact": 0.582807187955318 + } }, { - "name": "GET /api/orders", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api/orders" + }, + "averageResponseTime": 7624.5, + "transactionsPerMinute": 0.5, + "impact": 0.5802207655235637, "sample": { "@timestamp": "2020-06-29T06:48:35.450Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:39.483715Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:39.483715Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -2578,12 +3927,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["2"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:35 GMT"], - "Etag": ["W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "2" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:35 GMT" + ], + "Etag": [ + "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -2613,29 +3974,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413315450014 }, - "trace": { "id": "2da70ccf10599b271f65273d169cde9f" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413315450014 + }, + "trace": { + "id": "2da70ccf10599b271f65273d169cde9f" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 8784 }, + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 8784 + }, "id": "a3f4a4f339758440", "name": "GET /api/orders", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "request" }, "url": { @@ -2646,42 +4030,67 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 8800, - "averageResponseTime": 7624.5, - "transactionsPerMinute": 0.5, - "impact": 0.5802207655235637 + } }, { - "name": "GET /api/orders/:id", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api/orders/:id" + }, + "averageResponseTime": 4749.666666666667, + "transactionsPerMinute": 0.75, + "impact": 0.5227447114845778, "sample": { "@timestamp": "2020-06-29T06:48:35.951Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:39.484133Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:39.484133Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -2691,10 +4100,18 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["0"], - "Date": ["Mon, 29 Jun 2020 06:48:35 GMT"], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "0" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:35 GMT" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 404 }, @@ -2724,29 +4141,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413315951017 }, - "trace": { "id": "95979caa80e6622cbbb2d308800c3016" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413315951017 + }, + "trace": { + "id": "95979caa80e6622cbbb2d308800c3016" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 3210 }, + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 3210 + }, "id": "30344988dace0b43", "name": "GET /api/orders/:id", "result": "HTTP 4xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { @@ -2757,57 +4197,90 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 7184, - "averageResponseTime": 4749.666666666667, - "transactionsPerMinute": 0.75, - "impact": 0.5227447114845778 + } }, { - "name": "APIRestController#products", + "key": { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#products" + }, + "averageResponseTime": 6787, + "transactionsPerMinute": 0.5, + "impact": 0.4839483750082622, "sample": { - "@timestamp": "2020-06-29T06:48:27.824Z", + "@timestamp": "2020-06-29T06:48:13.595Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:36.087688Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:15.755614Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, "headers": { - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:27 GMT"], - "Transfer-Encoding": ["chunked"] + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:12 GMT" + ], + "Transfer-Encoding": [ + "chunked" + ] }, "headers_sent": true, "status_code": 200 @@ -2822,29 +4295,56 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Spring Web MVC", "version": "5.0.6.RELEASE" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Spring Web MVC", + "version": "5.0.6.RELEASE" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413307824008 }, - "trace": { "id": "a6adb17bd5a5d1c0eabb9f36cb626dd5" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413293595007 + }, + "trace": { + "id": "8519b6c3dbc32a0582228506526e1d74" + }, "transaction": { - "duration": { "us": 5645 }, - "id": "df3e726eaa003d96", + "duration": { + "us": 7929 + }, + "id": "b0354de660cd3698", "name": "APIRestController#products", "result": "HTTP 2xx", "sampled": true, - "span_count": { "dropped": 0, "started": 3 }, + "span_count": { + "dropped": 0, + "started": 3 + }, "type": "request" }, "url": { @@ -2855,40 +4355,61 @@ "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 7904, - "averageResponseTime": 6787, - "transactionsPerMinute": 0.5, - "impact": 0.4839483750082622 + } }, { - "name": "GET /api/products/:id/customers", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /api/products/:id/customers" + }, + "averageResponseTime": 4757, + "transactionsPerMinute": 0.5, + "impact": 0.25059559560997896, "sample": { - "@timestamp": "2020-06-29T06:48:41.956Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:48:22.977Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.994692Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:27.000765Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -2898,12 +4419,24 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["2"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:41 GMT"], - "Etag": ["W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "2" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:22 GMT" + ], + "Etag": [ + "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -2933,90 +4466,146 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413321956016 }, - "trace": { "id": "f735ac5fca8f83eebc748f4a2e009e61" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413302977008 + }, + "trace": { + "id": "da8f22fe652ccb6680b3029ab6efd284" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 3896 }, - "id": "b24ce95c855f83a4", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 5618 + }, + "id": "bc51c1523afaf57a", "name": "GET /api/products/:id/customers", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { "domain": "opbeans-node", - "full": "http://opbeans-node:3000/api/products/5/customers", - "original": "/api/products/5/customers", - "path": "/api/products/5/customers", + "full": "http://opbeans-node:3000/api/products/3/customers", + "original": "/api/products/3/customers", + "path": "/api/products/3/customers", "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 5616, - "averageResponseTime": 4757, - "transactionsPerMinute": 0.5, - "impact": 0.25059559560997896 + } }, { - "name": "APIRestController#product", + "key": { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#product" + }, + "averageResponseTime": 4713.5, + "transactionsPerMinute": 0.5, + "impact": 0.24559517890858723, "sample": { - "@timestamp": "2020-06-29T06:48:41.941Z", + "@timestamp": "2020-06-29T06:48:36.383Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:46.709268Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:46.666467Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, "headers": { - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:41 GMT"], - "Transfer-Encoding": ["chunked"] + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:36 GMT" + ], + "Transfer-Encoding": [ + "chunked" + ] }, "headers_sent": true, "status_code": 200 @@ -3031,81 +4620,131 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Spring Web MVC", "version": "5.0.6.RELEASE" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Spring Web MVC", + "version": "5.0.6.RELEASE" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413321941007 }, - "trace": { "id": "88a2b9ca970cdd38dfa1b5646d26b897" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413316383008 + }, + "trace": { + "id": "386b450aef87fc079b20136eda542af1" + }, "transaction": { - "duration": { "us": 4539 }, - "id": "24ee0e4812cfed62", + "duration": { + "us": 4888 + }, + "id": "5a4aa02158b5658c", "name": "APIRestController#product", "result": "HTTP 2xx", "sampled": true, - "span_count": { "dropped": 0, "started": 2 }, + "span_count": { + "dropped": 0, + "started": 3 + }, "type": "request" }, "url": { "domain": "172.18.0.6", - "full": "http://172.18.0.6:3000/api/products/4", - "path": "/api/products/4", + "full": "http://172.18.0.6:3000/api/products/1", + "path": "/api/products/1", "port": 3000, "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 4864, - "averageResponseTime": 4713.5, - "transactionsPerMinute": 0.5, - "impact": 0.24559517890858723 + } }, { - "name": "APIRestController#order", + "key": { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#order" + }, + "averageResponseTime": 3392.5, + "transactionsPerMinute": 0.5, + "impact": 0.09374344413758617, "sample": { - "@timestamp": "2020-06-29T06:48:33.314Z", + "@timestamp": "2020-06-29T06:48:07.416Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:36.137777Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:15.534378Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, @@ -3122,88 +4761,144 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Spring Web MVC", "version": "5.0.6.RELEASE" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Spring Web MVC", + "version": "5.0.6.RELEASE" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413313314007 }, - "trace": { "id": "aaf67f944393124080d1e4de804dc6eb" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413287416007 + }, + "trace": { + "id": "25c46380df3d44a192ed07279a08b329" + }, "transaction": { - "duration": { "us": 2503 }, - "id": "f7f9f5e0f8a3a0d4", + "duration": { + "us": 4282 + }, + "id": "d4d5b23c685d2ee5", "name": "APIRestController#order", "result": "HTTP 2xx", "sampled": true, - "span_count": { "dropped": 0, "started": 1 }, + "span_count": { + "dropped": 0, + "started": 1 + }, "type": "request" }, "url": { "domain": "172.18.0.6", - "full": "http://172.18.0.6:3000/api/orders/906", - "path": "/api/orders/906", + "full": "http://172.18.0.6:3000/api/orders/391", + "path": "/api/orders/391", "port": 3000, "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 4272, - "averageResponseTime": 3392.5, - "transactionsPerMinute": 0.5, - "impact": 0.09374344413758617 + } }, { - "name": "APIRestController#orders", + "key": { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#orders" + }, + "averageResponseTime": 3147, + "transactionsPerMinute": 0.5, + "impact": 0.06552270160444405, "sample": { - "@timestamp": "2020-06-29T06:48:39.500Z", + "@timestamp": "2020-06-29T06:48:16.028Z", "agent": { "ephemeral_id": "222af346-6dd9-45ef-ac85-d86b67edd2de", "name": "java", "version": "1.17.1-SNAPSHOT" }, - "client": { "ip": "172.18.0.9" }, + "client": { + "ip": "172.18.0.9" + }, "container": { "id": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:46.706280Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:25.800962Z" + }, "host": { "architecture": "amd64", "hostname": "918ebbd99b4f", "ip": "172.18.0.6", "name": "918ebbd99b4f", - "os": { "platform": "Linux" } + "os": { + "platform": "Linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Host": ["172.18.0.6:3000"], - "User-Agent": ["Python/3.7 aiohttp/3.3.2"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Host": [ + "172.18.0.6:3000" + ], + "User-Agent": [ + "Python/3.7 aiohttp/3.3.2" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "172.18.0.9" } + "socket": { + "encrypted": false, + "remote_address": "172.18.0.9" + } }, "response": { "finished": true, "headers": { - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:38 GMT"], - "Transfer-Encoding": ["chunked"] + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:15 GMT" + ], + "Transfer-Encoding": [ + "chunked" + ] }, "headers_sent": true, "status_code": 200 @@ -3218,29 +4913,56 @@ "version": "8.0.0", "version_major": 8 }, - "process": { "pid": 6, "ppid": 1, "title": "/opt/java/openjdk/bin/java" }, - "processor": { "event": "transaction", "name": "transaction" }, + "process": { + "pid": 6, + "ppid": 1, + "title": "/opt/java/openjdk/bin/java" + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "Spring Web MVC", "version": "5.0.6.RELEASE" }, - "language": { "name": "Java", "version": "11.0.7" }, + "framework": { + "name": "Spring Web MVC", + "version": "5.0.6.RELEASE" + }, + "language": { + "name": "Java", + "version": "11.0.7" + }, "name": "opbeans-java", "node": { "name": "918ebbd99b4f40003cf5713c080bb8120fa3bbe7ac4a96acb3aec558ced91ec0" }, - "runtime": { "name": "Java", "version": "11.0.7" }, + "runtime": { + "name": "Java", + "version": "11.0.7" + }, "version": "None" }, - "source": { "ip": "172.18.0.9" }, - "timestamp": { "us": 1593413319500008 }, - "trace": { "id": "f89b02f09a2e7a7f2cc3478f53d4a495" }, + "source": { + "ip": "172.18.0.9" + }, + "timestamp": { + "us": 1593413296028008 + }, + "trace": { + "id": "4110227ecacbccf79894165ae5df932d" + }, "transaction": { - "duration": { "us": 3391 }, - "id": "41c8c4300ee2ccda", + "duration": { + "us": 2903 + }, + "id": "8e3732f0f0da942b", "name": "APIRestController#orders", "result": "HTTP 2xx", "sampled": true, - "span_count": { "dropped": 0, "started": 2 }, + "span_count": { + "dropped": 0, + "started": 1 + }, "type": "request" }, "url": { @@ -3251,40 +4973,61 @@ "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "Python/3.7 aiohttp/3.3.2" } - }, - "p95": 3376, - "averageResponseTime": 3147, - "transactionsPerMinute": 0.5, - "impact": 0.06552270160444405 + } }, { - "name": "GET /throw-error", + "key": { + "service.name": "opbeans-node", + "transaction.name": "GET /throw-error" + }, + "averageResponseTime": 2577, + "transactionsPerMinute": 0.5, + "impact": 0, "sample": { - "@timestamp": "2020-06-29T06:48:42.954Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, + "@timestamp": "2020-06-29T06:48:19.975Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.996435Z" }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:21.012520Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", "socket": { @@ -3294,13 +5037,27 @@ }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["148"], - "Content-Security-Policy": ["default-src 'none'"], - "Content-Type": ["text/html; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:42 GMT"], - "X-Content-Type-Options": ["nosniff"], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "148" + ], + "Content-Security-Policy": [ + "default-src 'none'" + ], + "Content-Type": [ + "text/html; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:19 GMT" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 500 }, @@ -3330,29 +5087,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413322954016 }, - "trace": { "id": "9d5aee7443a43db9820f622a10dfac6e" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413299975019 + }, + "trace": { + "id": "106f3a55b0b0ea327d1bbe4be66c3bcc" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 1928 }, - "id": "8e6fc8c3f99e8fc9", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 3226 + }, + "id": "247b9141552a9e73", "name": "GET /throw-error", "result": "HTTP 5xx", "sampled": true, - "span_count": { "started": 0 }, + "span_count": { + "started": 0 + }, "type": "request" }, "url": { @@ -3363,16 +5143,18 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 3224, - "averageResponseTime": 2577, - "transactionsPerMinute": 0.5, - "impact": 0 + } } ] diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts index e96cb20a68fda..b4a037436adb8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; +import { sortBy, omit } from 'lodash'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import expectTopTraces from './expectation/top_traces.expectation.json'; @@ -46,8 +47,28 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.items.length).to.be(33); }); - it('returns the correct buckets and samples', async () => { - expect(response.body.items).to.eql(expectTopTraces); + it('returns the correct buckets', async () => { + const responseWithoutSamples = sortBy( + response.body.items.map((item: any) => omit(item, 'sample')), + 'impact' + ); + + const expectedTracesWithoutSamples = sortBy( + expectTopTraces.map((item: any) => omit(item, 'sample')), + 'impact' + ); + + expect(responseWithoutSamples).to.eql(expectedTracesWithoutSamples); + }); + + it('returns a sample', async () => { + // sample should provide enough information to deeplink to a transaction detail page + response.body.items.forEach((item: any) => { + expect(item.sample.trace.id).to.be.an('string'); + expect(item.sample.transaction.id).to.be.an('string'); + expect(item.sample.service.name).to.be(item.key['service.name']); + expect(item.sample.transaction.name).to.be(item.key['transaction.name']); + }); }); }); }); diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/top_transaction_groups.json b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/top_transaction_groups.json index 7d314e75e4d1d..29c55d4ef1b5c 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/top_transaction_groups.json +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/top_transaction_groups.json @@ -1,38 +1,86 @@ { "items": [ { - "name": "GET /api", + "key": "GET /api", + "averageResponseTime": 51175.73170731707, + "transactionsPerMinute": 10.25, + "impact": 100, + "p95": 259040, "sample": { - "@timestamp": "2020-06-29T06:48:41.454Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.992834Z" }, + "@timestamp": "2020-06-29T06:48:06.862Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.8" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:08.305742Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Connection": [ + "keep-alive" + ], + "Host": [ + "opbeans-node:3000" + ], + "Referer": [ + "http://opbeans-node:3000/dashboard" + ], + "Traceparent": [ + "00-ca86ffcac7753ec8733933bd8fd45d11-5dcb98c9c9021cfc-01" + ], + "User-Agent": [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.8" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:41 GMT"], - "Transfer-Encoding": ["chunked"], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:06 GMT" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -52,6 +100,9 @@ "version": "8.0.0", "version_major": 8 }, + "parent": { + "id": "5dcb98c9c9021cfc" + }, "process": { "args": [ "/usr/local/bin/node", @@ -62,87 +113,164 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413321454009 }, - "trace": { "id": "0507830eeff93f7bf1a354e4031097b3" }, + "source": { + "ip": "172.18.0.8" + }, + "timestamp": { + "us": 1593413286862021 + }, + "trace": { + "id": "ca86ffcac7753ec8733933bd8fd45d11" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 8334 }, - "id": "878250a8b937445d", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 15738 + }, + "id": "c95371db21c6f407", "name": "GET /api", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { "domain": "opbeans-node", - "full": "http://opbeans-node:3000/api/products/6", - "original": "/api/products/6", - "path": "/api/products/6", + "full": "http://opbeans-node:3000/api/products/top", + "original": "/api/products/top", + "path": "/api/products/top", "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, - "name": "Other", - "original": "workload/2.4.3" + "device": { + "name": "Other" + }, + "name": "HeadlessChrome", + "original": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36", + "os": { + "name": "Linux" + }, + "version": "79.0.3945" } - }, - "p95": 259040, - "averageResponseTime": 51175.73170731707, - "transactionsPerMinute": 10.25, - "impact": 100 + } }, { - "name": "POST /api/orders", + "key": "POST /api/orders", + "averageResponseTime": 270684, + "transactionsPerMinute": 0.25, + "impact": 12.686265169840583, + "p95": 270336, "sample": { "@timestamp": "2020-06-29T06:48:39.953Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.991549Z" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:43.991549Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { - "body": { "original": "[REDACTED]" }, + "body": { + "original": "[REDACTED]" + }, "headers": { - "Accept": ["application/json"], - "Connection": ["close"], - "Content-Length": ["129"], - "Content-Type": ["application/json"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Accept": [ + "application/json" + ], + "Connection": [ + "close" + ], + "Content-Length": [ + "129" + ], + "Content-Type": [ + "application/json" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "post", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["13"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:40 GMT"], - "Etag": ["W/\"d-eEOWU4Cnr5DZ23ErRUeYu9oOIks\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "13" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:40 GMT" + ], + "Etag": [ + "W/\"d-eEOWU4Cnr5DZ23ErRUeYu9oOIks\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -172,27 +300,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413319953033 }, - "trace": { "id": "52b8fda5f6df745b990740ba18378620" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413319953033 + }, + "trace": { + "id": "52b8fda5f6df745b990740ba18378620" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 270684 }, + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 270684 + }, "id": "a3afc2a112e9c893", "name": "POST /api/orders", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 16 }, + "span_count": { + "started": 16 + }, "type": "request" }, "url": { @@ -203,52 +356,92 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 270336, - "averageResponseTime": 270684, - "transactionsPerMinute": 0.25, - "impact": 12.686265169840583 + } }, { - "name": "GET /api/customers", + "key": "GET /api/customers", + "averageResponseTime": 16896.8, + "transactionsPerMinute": 1.25, + "impact": 3.790160870423129, + "p95": 26432, "sample": { - "@timestamp": "2020-06-29T06:48:37.952Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:39.492402Z" }, + "@timestamp": "2020-06-29T06:48:28.444Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:29.982737Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["186769"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:37 GMT"], - "Etag": ["W/\"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "186769" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:28 GMT" + ], + "Etag": [ + "W/\"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -278,27 +471,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413317952016 }, - "trace": { "id": "5d99327edae38ed735e8d7a6028cf719" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413308444015 + }, + "trace": { + "id": "792fb0b00256164e88b277ec40b65e14" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 16824 }, - "id": "071808429ec9d00b", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 26471 + }, + "id": "6c1f848752563d2b", "name": "GET /api/customers", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "request" }, "url": { @@ -309,52 +527,92 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 26432, - "averageResponseTime": 16896.8, - "transactionsPerMinute": 1.25, - "impact": 3.790160870423129 + } }, { - "name": "GET /log-message", + "key": "GET /log-message", + "averageResponseTime": 32667.5, + "transactionsPerMinute": 0.5, + "impact": 2.875276331059301, + "p95": 38528, "sample": { - "@timestamp": "2020-06-29T06:48:28.944Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:39.370695Z" }, + "@timestamp": "2020-06-29T06:48:25.944Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:29.976822Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["24"], - "Content-Type": ["text/html; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:28 GMT"], - "Etag": ["W/\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "24" + ], + "Content-Type": [ + "text/html; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:25 GMT" + ], + "Etag": [ + "W/\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 500 }, @@ -384,27 +642,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413308944016 }, - "trace": { "id": "afabe4cb397616f5ec35a2f3da49ba62" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413305944023 + }, + "trace": { + "id": "cd2ad726ad164d701c5d3103cbab0c81" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 26788 }, - "id": "cc8c4261387507cf", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 38547 + }, + "id": "9e41667eb64dea55", "name": "GET /log-message", "result": "HTTP 5xx", "sampled": true, - "span_count": { "started": 0 }, + "span_count": { + "started": 0 + }, "type": "request" }, "url": { @@ -415,51 +698,89 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 38528, - "averageResponseTime": 32667.5, - "transactionsPerMinute": 0.5, - "impact": 2.875276331059301 + } }, { - "name": "GET /*", + "key": "GET /*", + "averageResponseTime": 3262.95, + "transactionsPerMinute": 5, + "impact": 2.8716452680799467, + "p95": 4472, "sample": { - "@timestamp": "2020-06-29T06:48:42.454Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.995202Z" }, + "@timestamp": "2020-06-29T06:48:25.064Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:27.005197Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "Wget" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["813"], - "Content-Type": ["text/html"], - "Date": ["Mon, 29 Jun 2020 06:48:42 GMT"], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "813" + ], + "Content-Type": [ + "text/html" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:25 GMT" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -479,7 +800,9 @@ "version": "8.0.0", "version_major": 8 }, - "parent": { "id": "5baa6c3bedc93f9d" }, + "parent": { + "id": "f673ceaf4583f0f2" + }, "process": { "args": [ "/usr/local/bin/node", @@ -490,27 +813,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413322454015 }, - "trace": { "id": "022b01256b291a4c417199d91ec8755f" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413305064023 + }, + "trace": { + "id": "30c12f4d8ef77a5be1b4464e5d2235bc" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 1737 }, - "id": "eff3e45e0d9529d9", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 3004 + }, + "id": "18a00dfdb919a978", "name": "GET /*", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 0 }, + "span_count": { + "started": 0 + }, "type": "request" }, "url": { @@ -521,59 +869,104 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, - "name": "Other", - "original": "workload/2.4.3" + "device": { + "name": "Other" + }, + "name": "Wget", + "original": "Wget" } - }, - "p95": 4472, - "averageResponseTime": 3262.95, - "transactionsPerMinute": 5, - "impact": 2.8716452680799467 + } }, { - "name": "GET /api/orders", + "key": "GET /api/orders", + "averageResponseTime": 7615.625, + "transactionsPerMinute": 2, + "impact": 2.6645791239678345, + "p95": 11616, "sample": { - "@timestamp": "2020-06-29T06:48:40.106Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.6" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.988104Z" }, + "@timestamp": "2020-06-29T06:48:28.782Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.8" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:29.983252Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { "Accept": [ - "text/plain, application/json, application/x-jackson-smile, application/cbor, application/*+json, */*" + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Connection": [ + "keep-alive" ], - "Connection": ["keep-alive"], - "Elastic-Apm-Traceparent": [ - "00-90bd7780b32cc51a7f4c200b1e0c170f-5ff346d602ce27b0-01" + "Host": [ + "opbeans-node:3000" ], - "Host": ["opbeans-node:3000"], - "Traceparent": ["00-90bd7780b32cc51a7f4c200b1e0c170f-5ff346d602ce27b0-01"], - "User-Agent": ["Java/11.0.7"] + "Referer": [ + "http://opbeans-node:3000/orders" + ], + "Traceparent": [ + "00-978b56807e0b7a27cbc41a0dfb665f47-3358a24e09e23561-01" + ], + "User-Agent": [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.6" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.8" + } }, "response": { "headers": { - "Connection": ["keep-alive"], - "Content-Length": ["2"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:40 GMT"], - "Etag": ["W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""], - "X-Powered-By": ["Express"] + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "2" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:28 GMT" + ], + "Etag": [ + "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -593,7 +986,9 @@ "version": "8.0.0", "version_major": 8 }, - "parent": { "id": "5ff346d602ce27b0" }, + "parent": { + "id": "3358a24e09e23561" + }, "process": { "args": [ "/usr/local/bin/node", @@ -604,27 +999,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.6" }, - "timestamp": { "us": 1593413320106015 }, - "trace": { "id": "90bd7780b32cc51a7f4c200b1e0c170f" }, + "source": { + "ip": "172.18.0.8" + }, + "timestamp": { + "us": 1593413308782015 + }, + "trace": { + "id": "978b56807e0b7a27cbc41a0dfb665f47" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 7424 }, - "id": "f3dd00d12c594cba", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 7134 + }, + "id": "a6d8f3c5c98903e1", "name": "GET /api/orders", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "request" }, "url": { @@ -635,60 +1055,96 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Spider" }, - "name": "Java", - "original": "Java/11.0.7", - "version": "0.7." + "device": { + "name": "Other" + }, + "name": "HeadlessChrome", + "original": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36", + "os": { + "name": "Linux" + }, + "version": "79.0.3945" } - }, - "p95": 11616, - "averageResponseTime": 7615.625, - "transactionsPerMinute": 2, - "impact": 2.6645791239678345 + } }, { - "name": "GET /api/products", + "key": "GET /api/products", + "averageResponseTime": 8585, + "transactionsPerMinute": 1.75, + "impact": 2.624924094061731, + "p95": 22112, "sample": { - "@timestamp": "2020-06-29T06:48:27.452Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.6" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:29.978463Z" }, + "@timestamp": "2020-06-29T06:48:21.475Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:26.996210Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Accept": [ - "text/plain, application/json, application/x-jackson-smile, application/cbor, application/*+json, */*" + "Connection": [ + "close" ], - "Connection": ["keep-alive"], - "Elastic-Apm-Traceparent": [ - "00-27b168a328e0fd442377de8eaa0bf582-2c86873dedb66c2c-01" + "Host": [ + "opbeans-node:3000" ], - "Host": ["opbeans-node:3000"], - "Traceparent": ["00-27b168a328e0fd442377de8eaa0bf582-2c86873dedb66c2c-01"], - "User-Agent": ["Java/11.0.7"] + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.6" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["keep-alive"], - "Content-Length": ["1023"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:27 GMT"], - "Etag": ["W/\"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "1023" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:21 GMT" + ], + "Etag": [ + "W/\"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -708,7 +1164,6 @@ "version": "8.0.0", "version_major": 8 }, - "parent": { "id": "2c86873dedb66c2c" }, "process": { "args": [ "/usr/local/bin/node", @@ -719,27 +1174,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.6" }, - "timestamp": { "us": 1593413307452013 }, - "trace": { "id": "27b168a328e0fd442377de8eaa0bf582" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413301475015 + }, + "trace": { + "id": "389b26b16949c7f783223de4f14b788c" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 4292 }, - "id": "141ecc2dfd55eeea", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 6775 + }, + "id": "d2d4088a0b104fb4", "name": "GET /api/products", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "request" }, "url": { @@ -750,53 +1230,92 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Spider" }, - "name": "Java", - "original": "Java/11.0.7", - "version": "0.7." + "device": { + "name": "Other" + }, + "name": "Other", + "original": "workload/2.4.3" } - }, - "p95": 22112, - "averageResponseTime": 8585, - "transactionsPerMinute": 1.75, - "impact": 2.624924094061731 + } }, { - "name": "GET /api/products/:id", + "key": "GET /api/products/:id", + "averageResponseTime": 13516.5, + "transactionsPerMinute": 1, + "impact": 2.3368756900811305, + "p95": 37856, "sample": { - "@timestamp": "2020-06-29T06:48:24.977Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:27.004717Z" }, + "@timestamp": "2020-06-29T06:47:57.555Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:47:59.085077Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["231"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:24 GMT"], - "Etag": ["W/\"e7-kkuzj37GZDzXDh0CWqh5Gan0VO4\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "231" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:47:57 GMT" + ], + "Etag": [ + "W/\"e7-6JlJegaJ+ir0C8I8EmmOjms1dnc\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -822,87 +1341,152 @@ "/usr/local/lib/node_modules/pm2/lib/ProcessContainer.js", "ecosystem-workload.config.js" ], - "pid": 137, + "pid": 87, "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413304977014 }, - "trace": { "id": "b9b290bca14c99962fa9a1c509401630" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413277555176 + }, + "trace": { + "id": "8365e1763f19e4067b88521d4d9247a0" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 4482 }, - "id": "b8d8284ff1fc25d6", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 37709 + }, + "id": "be2722a418272f10", "name": "GET /api/products/:id", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { "domain": "opbeans-node", - "full": "http://opbeans-node:3000/api/products/3", - "original": "/api/products/3", - "path": "/api/products/3", + "full": "http://opbeans-node:3000/api/products/1", + "original": "/api/products/1", + "path": "/api/products/1", "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 37856, - "averageResponseTime": 13516.5, - "transactionsPerMinute": 1, - "impact": 2.3368756900811305 + } }, { - "name": "GET /api/types", + "key": "GET /api/types", + "averageResponseTime": 26992.5, + "transactionsPerMinute": 0.5, + "impact": 2.3330057413794503, + "p95": 45248, "sample": { - "@timestamp": "2020-06-29T06:48:26.443Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:29.977518Z" }, + "@timestamp": "2020-06-29T06:47:52.935Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:47:55.471071Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["112"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:26 GMT"], - "Etag": ["W/\"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "112" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:47:52 GMT" + ], + "Etag": [ + "W/\"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -928,31 +1512,56 @@ "/usr/local/lib/node_modules/pm2/lib/ProcessContainer.js", "ecosystem-workload.config.js" ], - "pid": 137, + "pid": 63, "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413306443014 }, - "trace": { "id": "be3f4eb50d253afc032c90eacaa85072" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413272935117 + }, + "trace": { + "id": "2946c536a33d163d0c984d00d1f3839a" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 8892 }, - "id": "ccce129bb8c6b125", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 45093 + }, + "id": "103482fda88b9400", "name": "GET /api/types", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "request" }, "url": { @@ -963,55 +1572,101 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 45248, - "averageResponseTime": 26992.5, - "transactionsPerMinute": 0.5, - "impact": 2.3330057413794503 + } }, { - "name": "GET static file", + "key": "GET static file", + "averageResponseTime": 3492.9285714285716, + "transactionsPerMinute": 3.5, + "impact": 2.0901067389184496, + "p95": 11900, "sample": { - "@timestamp": "2020-06-29T06:48:40.953Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.992454Z" }, + "@timestamp": "2020-06-29T06:47:53.427Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:47:55.472070Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Accept-Ranges": ["bytes"], - "Cache-Control": ["public, max-age=0"], - "Connection": ["close"], - "Content-Length": ["15086"], - "Content-Type": ["image/x-icon"], - "Date": ["Mon, 29 Jun 2020 06:48:40 GMT"], - "Etag": ["W/\"3aee-1725aff14f0\""], - "Last-Modified": ["Thu, 28 May 2020 11:16:06 GMT"], - "X-Powered-By": ["Express"] + "Accept-Ranges": [ + "bytes" + ], + "Cache-Control": [ + "public, max-age=0" + ], + "Connection": [ + "close" + ], + "Content-Length": [ + "15086" + ], + "Content-Type": [ + "image/x-icon" + ], + "Date": [ + "Mon, 29 Jun 2020 06:47:53 GMT" + ], + "Etag": [ + "W/\"3aee-1725aff14f0\"" + ], + "Last-Modified": [ + "Thu, 28 May 2020 11:16:06 GMT" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -1031,30 +1686,53 @@ "/usr/local/lib/node_modules/pm2/lib/ProcessContainer.js", "ecosystem-workload.config.js" ], - "pid": 137, + "pid": 63, "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413320953016 }, - "trace": { "id": "292393440bbe04385f3c2e3715ac35fe" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413273427016 + }, + "trace": { + "id": "ec8a804fedf28fcf81d5682d69a16970" + }, "transaction": { - "duration": { "us": 1671 }, - "id": "d1d964ca1865dce3", + "duration": { + "us": 4934 + }, + "id": "ab90a62901b770e6", "name": "GET static file", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 0 }, + "span_count": { + "started": 0 + }, "type": "request" }, "url": { @@ -1066,56 +1744,86 @@ "scheme": "http" }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 11900, - "averageResponseTime": 3492.9285714285716, - "transactionsPerMinute": 3.5, - "impact": 2.0901067389184496 + } }, { - "name": "GET /api/products/top", + "key": "GET /api/products/top", + "averageResponseTime": 22958.5, + "transactionsPerMinute": 0.5, + "impact": 1.9475397398343375, + "p95": 33216, "sample": { - "@timestamp": "2020-06-29T06:48:18.211Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.8" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:21.007197Z" }, + "@timestamp": "2020-06-29T06:48:01.200Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:02.734903Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "Connection": ["keep-alive"], - "Host": ["opbeans-node:3000"], - "Referer": ["http://opbeans-node:3000/dashboard"], - "Traceparent": ["00-4879105b2de793ca54ce7299aff0f5ce-0d67fab9c9dec84d-01"], + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36" + "workload/2.4.3" ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.8" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["keep-alive"], - "Content-Length": ["2"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:18 GMT"], - "Etag": ["W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "2" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:01 GMT" + ], + "Etag": [ + "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -1135,38 +1843,62 @@ "version": "8.0.0", "version_major": 8 }, - "parent": { "id": "0d67fab9c9dec84d" }, "process": { "args": [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainer.js", "ecosystem-workload.config.js" ], - "pid": 137, + "pid": 115, "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.8" }, - "timestamp": { "us": 1593413298211013 }, - "trace": { "id": "4879105b2de793ca54ce7299aff0f5ce" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413281200133 + }, + "trace": { + "id": "195f32efeb6f91e2f71b6bc8bb74ae3a" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 12820 }, - "id": "b15b12c837ab8b89", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 33097 + }, + "id": "22e72956dfc8967a", "name": "GET /api/products/top", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { @@ -1177,56 +1909,103 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, - "name": "HeadlessChrome", - "original": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36", - "os": { "name": "Linux" }, - "version": "79.0.3945" + "device": { + "name": "Other" + }, + "name": "Other", + "original": "workload/2.4.3" } - }, - "p95": 33216, - "averageResponseTime": 22958.5, - "transactionsPerMinute": 0.5, - "impact": 1.9475397398343375 + } }, { - "name": "GET /api/stats", + "key": "GET /api/stats", + "averageResponseTime": 7105.333333333333, + "transactionsPerMinute": 1.5, + "impact": 1.7905918202662048, + "p95": 15136, "sample": { - "@timestamp": "2020-06-29T06:48:39.451Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.984824Z" }, + "@timestamp": "2020-06-29T06:48:21.150Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.8" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:26.993832Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Connection": [ + "keep-alive" + ], + "Host": [ + "opbeans-node:3000" + ], + "If-None-Match": [ + "W/\"5c-6I+bqIiLxvkWuwBUnTxhBoK4lBk\"" + ], + "Referer": [ + "http://opbeans-node:3000/dashboard" + ], + "Traceparent": [ + "00-ee0ce8b38b8d5945829fc1c9432538bf-39d52cd5f528d363-01" + ], + "User-Agent": [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.8" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["92"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:39 GMT"], - "Etag": ["W/\"5c-6I+bqIiLxvkWuwBUnTxhBoK4lBk\""], - "X-Powered-By": ["Express"] + "Connection": [ + "keep-alive" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:21 GMT" + ], + "Etag": [ + "W/\"5c-6I+bqIiLxvkWuwBUnTxhBoK4lBk\"" + ], + "X-Powered-By": [ + "Express" + ] }, - "status_code": 200 + "status_code": 304 }, "version": "1.1" }, @@ -1244,6 +2023,9 @@ "version": "8.0.0", "version_major": 8 }, + "parent": { + "id": "39d52cd5f528d363" + }, "process": { "args": [ "/usr/local/bin/node", @@ -1254,27 +2036,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413319451016 }, - "trace": { "id": "a05787cb03a0af0863fab5e05de942f1" }, + "source": { + "ip": "172.18.0.8" + }, + "timestamp": { + "us": 1593413301150014 + }, + "trace": { + "id": "ee0ce8b38b8d5945829fc1c9432538bf" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 5050 }, - "id": "a7e004eba8f021ce", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 7273 + }, + "id": "05d5b62182c59a54", "name": "GET /api/stats", - "result": "HTTP 2xx", + "result": "HTTP 3xx", "sampled": true, - "span_count": { "started": 4 }, + "span_count": { + "started": 4 + }, "type": "request" }, "url": { @@ -1285,52 +2092,96 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, - "name": "Other", - "original": "workload/2.4.3" + "device": { + "name": "Other" + }, + "name": "HeadlessChrome", + "original": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36", + "os": { + "name": "Linux" + }, + "version": "79.0.3945" } - }, - "p95": 15136, - "averageResponseTime": 7105.333333333333, - "transactionsPerMinute": 1.5, - "impact": 1.7905918202662048 + } }, { - "name": "GET /log-error", + "key": "GET /log-error", + "averageResponseTime": 35846, + "transactionsPerMinute": 0.25, + "impact": 1.466376117925459, + "p95": 35840, "sample": { "@timestamp": "2020-06-29T06:48:07.467Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:18.533253Z" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:18.533253Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["24"], - "Content-Type": ["text/html; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:07 GMT"], - "Etag": ["W/\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "24" + ], + "Content-Type": [ + "text/html; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:07 GMT" + ], + "Etag": [ + "W/\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 500 }, @@ -1360,27 +2211,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413287467017 }, - "trace": { "id": "d518b2c4d72cd2aaf1e39bad7ebcbdbb" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413287467017 + }, + "trace": { + "id": "d518b2c4d72cd2aaf1e39bad7ebcbdbb" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 35846 }, + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 35846 + }, "id": "c7a30c1b076907ec", "name": "GET /log-error", "result": "HTTP 5xx", "sampled": true, - "span_count": { "started": 0 }, + "span_count": { + "started": 0 + }, "type": "request" }, "url": { @@ -1391,56 +2267,104 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 35840, - "averageResponseTime": 35846, - "transactionsPerMinute": 0.25, - "impact": 1.466376117925459 + } }, { - "name": "POST /api", + "key": "POST /api", + "averageResponseTime": 20011, + "transactionsPerMinute": 0.25, + "impact": 0.7098250353192541, + "p95": 19968, "sample": { "@timestamp": "2020-06-29T06:48:25.478Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:27.005671Z" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:27.005671Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { - "body": { "original": "[REDACTED]" }, + "body": { + "original": "[REDACTED]" + }, "headers": { - "Accept": ["application/json"], - "Connection": ["close"], - "Content-Length": ["129"], - "Content-Type": ["application/json"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Accept": [ + "application/json" + ], + "Connection": [ + "close" + ], + "Content-Length": [ + "129" + ], + "Content-Type": [ + "application/json" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "post", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Allow": ["GET"], - "Connection": ["close"], - "Content-Type": ["application/json;charset=UTF-8"], - "Date": ["Mon, 29 Jun 2020 06:48:25 GMT"], - "Transfer-Encoding": ["chunked"], - "X-Powered-By": ["Express"] + "Allow": [ + "GET" + ], + "Connection": [ + "close" + ], + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:25 GMT" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 405 }, @@ -1470,27 +2394,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413305478010 }, - "trace": { "id": "4bd9027dd1e355ec742970e2d6333124" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413305478010 + }, + "trace": { + "id": "4bd9027dd1e355ec742970e2d6333124" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 20011 }, + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 20011 + }, "id": "94104435cf151478", "name": "POST /api", "result": "HTTP 4xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { @@ -1501,52 +2450,92 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 19968, - "averageResponseTime": 20011, - "transactionsPerMinute": 0.25, - "impact": 0.7098250353192541 + } }, { - "name": "GET /api/types/:id", + "key": "GET /api/types/:id", + "averageResponseTime": 8181, + "transactionsPerMinute": 0.5, + "impact": 0.5354862351657939, + "p95": 10080, "sample": { - "@timestamp": "2020-06-29T06:48:12.972Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:18.543436Z" }, + "@timestamp": "2020-06-29T06:47:53.928Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:47:55.472718Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["205"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:12 GMT"], - "Etag": ["W/\"cd-pFMi1QOVY6YqWe+nwcbZVviCths\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "205" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:47:53 GMT" + ], + "Etag": [ + "W/\"cd-pFMi1QOVY6YqWe+nwcbZVviCths\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -1572,31 +2561,56 @@ "/usr/local/lib/node_modules/pm2/lib/ProcessContainer.js", "ecosystem-workload.config.js" ], - "pid": 137, + "pid": 63, "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413292972011 }, - "trace": { "id": "aea65cef5f902dda5d8e38f9fb38864d" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413273928016 + }, + "trace": { + "id": "0becaafb422bfeb69e047bf7153aa469" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 6300 }, - "id": "a5bdbe43ac05fae2", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 10062 + }, + "id": "0cee4574091bda3b", "name": "GET /api/types/:id", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 2 }, + "span_count": { + "started": 2 + }, "type": "request" }, "url": { @@ -1607,50 +2621,86 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 10080, - "averageResponseTime": 8181, - "transactionsPerMinute": 0.5, - "impact": 0.5354862351657939 + } }, { - "name": "GET /api/orders/:id", + "key": "GET /api/orders/:id", + "averageResponseTime": 4749.666666666667, + "transactionsPerMinute": 0.75, + "impact": 0.43453312891085794, + "p95": 7184, "sample": { "@timestamp": "2020-06-29T06:48:35.951Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:39.484133Z" }, + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:39.484133Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["0"], - "Date": ["Mon, 29 Jun 2020 06:48:35 GMT"], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "0" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:35 GMT" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 404 }, @@ -1680,27 +2730,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413315951017 }, - "trace": { "id": "95979caa80e6622cbbb2d308800c3016" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413315951017 + }, + "trace": { + "id": "95979caa80e6622cbbb2d308800c3016" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 3210 }, + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 3210 + }, "id": "30344988dace0b43", "name": "GET /api/orders/:id", "result": "HTTP 4xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { @@ -1711,52 +2786,92 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 7184, - "averageResponseTime": 4749.666666666667, - "transactionsPerMinute": 0.75, - "impact": 0.43453312891085794 + } }, { - "name": "GET /api/products/:id/customers", + "key": "GET /api/products/:id/customers", + "averageResponseTime": 4757, + "transactionsPerMinute": 0.5, + "impact": 0.20830834986820673, + "p95": 5616, "sample": { - "@timestamp": "2020-06-29T06:48:41.956Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.994692Z" }, + "@timestamp": "2020-06-29T06:48:22.977Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:27.000765Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["2"], - "Content-Type": ["application/json; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:41 GMT"], - "Etag": ["W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "2" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:22 GMT" + ], + "Etag": [ + "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\"" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 200 }, @@ -1786,84 +2901,151 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413321956016 }, - "trace": { "id": "f735ac5fca8f83eebc748f4a2e009e61" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413302977008 + }, + "trace": { + "id": "da8f22fe652ccb6680b3029ab6efd284" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 3896 }, - "id": "b24ce95c855f83a4", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 5618 + }, + "id": "bc51c1523afaf57a", "name": "GET /api/products/:id/customers", "result": "HTTP 2xx", "sampled": true, - "span_count": { "started": 1 }, + "span_count": { + "started": 1 + }, "type": "request" }, "url": { "domain": "opbeans-node", - "full": "http://opbeans-node:3000/api/products/5/customers", - "original": "/api/products/5/customers", - "path": "/api/products/5/customers", + "full": "http://opbeans-node:3000/api/products/3/customers", + "original": "/api/products/3/customers", + "path": "/api/products/3/customers", "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 5616, - "averageResponseTime": 4757, - "transactionsPerMinute": 0.5, - "impact": 0.20830834986820673 + } }, { - "name": "GET /throw-error", + "key": "GET /throw-error", + "averageResponseTime": 2577, + "transactionsPerMinute": 0.5, + "impact": 0, + "p95": 3224, "sample": { - "@timestamp": "2020-06-29T06:48:42.954Z", - "agent": { "name": "nodejs", "version": "3.6.1" }, - "client": { "ip": "172.18.0.7" }, - "container": { "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "ecs": { "version": "1.5.0" }, - "event": { "ingested": "2020-06-29T06:48:43.996435Z" }, + "@timestamp": "2020-06-29T06:48:19.975Z", + "agent": { + "name": "nodejs", + "version": "3.6.1" + }, + "client": { + "ip": "172.18.0.7" + }, + "container": { + "id": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "ecs": { + "version": "1.5.0" + }, + "event": { + "ingested": "2020-06-29T06:48:21.012520Z" + }, "host": { "architecture": "x64", "hostname": "41712ded148f", "ip": "172.18.0.7", "name": "41712ded148f", - "os": { "platform": "linux" } + "os": { + "platform": "linux" + } }, "http": { "request": { "headers": { - "Connection": ["close"], - "Host": ["opbeans-node:3000"], - "User-Agent": ["workload/2.4.3"] + "Connection": [ + "close" + ], + "Host": [ + "opbeans-node:3000" + ], + "User-Agent": [ + "workload/2.4.3" + ] }, "method": "get", - "socket": { "encrypted": false, "remote_address": "::ffff:172.18.0.7" } + "socket": { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7" + } }, "response": { "headers": { - "Connection": ["close"], - "Content-Length": ["148"], - "Content-Security-Policy": ["default-src 'none'"], - "Content-Type": ["text/html; charset=utf-8"], - "Date": ["Mon, 29 Jun 2020 06:48:42 GMT"], - "X-Content-Type-Options": ["nosniff"], - "X-Powered-By": ["Express"] + "Connection": [ + "close" + ], + "Content-Length": [ + "148" + ], + "Content-Security-Policy": [ + "default-src 'none'" + ], + "Content-Type": [ + "text/html; charset=utf-8" + ], + "Date": [ + "Mon, 29 Jun 2020 06:48:19 GMT" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Powered-By": [ + "Express" + ] }, "status_code": 500 }, @@ -1893,27 +3075,52 @@ "ppid": 1, "title": "node /app/server.js" }, - "processor": { "event": "transaction", "name": "transaction" }, + "processor": { + "event": "transaction", + "name": "transaction" + }, "service": { "environment": "production", - "framework": { "name": "express", "version": "4.17.1" }, - "language": { "name": "javascript" }, + "framework": { + "name": "express", + "version": "4.17.1" + }, + "language": { + "name": "javascript" + }, "name": "opbeans-node", - "node": { "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" }, - "runtime": { "name": "node", "version": "12.18.1" }, + "node": { + "name": "41712ded148f30ee09a13421780eec4304bf5049b82a0d8dbc877893be6799e4" + }, + "runtime": { + "name": "node", + "version": "12.18.1" + }, "version": "1.0.0" }, - "source": { "ip": "172.18.0.7" }, - "timestamp": { "us": 1593413322954016 }, - "trace": { "id": "9d5aee7443a43db9820f622a10dfac6e" }, + "source": { + "ip": "172.18.0.7" + }, + "timestamp": { + "us": 1593413299975019 + }, + "trace": { + "id": "106f3a55b0b0ea327d1bbe4be66c3bcc" + }, "transaction": { - "custom": { "shoppingBasketCount": 42 }, - "duration": { "us": 1928 }, - "id": "8e6fc8c3f99e8fc9", + "custom": { + "shoppingBasketCount": 42 + }, + "duration": { + "us": 3226 + }, + "id": "247b9141552a9e73", "name": "GET /throw-error", "result": "HTTP 5xx", "sampled": true, - "span_count": { "started": 0 }, + "span_count": { + "started": 0 + }, "type": "request" }, "url": { @@ -1924,19 +3131,21 @@ "port": 3000, "scheme": "http" }, - "user": { "email": "kimchy@elastic.co", "id": "42", "name": "kimchy" }, + "user": { + "email": "kimchy@elastic.co", + "id": "42", + "name": "kimchy" + }, "user_agent": { - "device": { "name": "Other" }, + "device": { + "name": "Other" + }, "name": "Other", "original": "workload/2.4.3" } - }, - "p95": 3224, - "averageResponseTime": 2577, - "transactionsPerMinute": 0.5, - "impact": 0 + } } ], "isAggregationAccurate": true, - "bucketSize": 100 -} + "bucketSize": 1000 +} \ No newline at end of file diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts index 43b2ad5414c7a..94559a3e4aa54 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts @@ -4,9 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; +import { sortBy } from 'lodash'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import expectedTransactionGroups from './expectation/top_transaction_groups.json'; +function sortTransactionGroups(items: any[]) { + return sortBy(items, 'impact'); +} + +function omitSampleFromTransactionGroups(items: any[]) { + return sortTransactionGroups(items).map(({ sample, ...item }) => ({ ...item })); +} + export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); @@ -48,15 +57,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the correct buckets (when ignoring samples)', async () => { - function omitSample(items: any[]) { - return items.map(({ sample, ...item }) => ({ ...item })); - } - - expect(omitSample(response.body.items)).to.eql(omitSample(expectedTransactionGroups.items)); + expect(omitSampleFromTransactionGroups(response.body.items)).to.eql( + omitSampleFromTransactionGroups(expectedTransactionGroups.items) + ); }); it('returns the correct buckets and samples', async () => { - expect(response.body.items).to.eql(expectedTransactionGroups.items); + // sample should provide enough information to deeplink to a transaction detail page + response.body.items.forEach((item: any) => { + expect(item.sample.trace.id).to.be.an('string'); + expect(item.sample.transaction.id).to.be.an('string'); + expect(item.sample.service.name).to.be('opbeans-node'); + expect(item.sample.transaction.name).to.be(item.key); + }); }); }); }); From fb4ee91f0ca40d2158d0756e1aac18b01ba69761 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Tue, 28 Jul 2020 09:55:57 -0400 Subject: [PATCH 34/75] [Security Solution][Resolver] Fix resolver isStart event bug (#73357) * Check if category is array * Adding more tests and renaming to isStart * Handling the case where start is not at the front --- .../common/endpoint/generate_data.test.ts | 21 ++-- .../common/endpoint/generate_data.ts | 25 +++-- .../common/endpoint/models/event.test.ts | 96 ++++++++++++++----- .../common/endpoint/models/event.ts | 7 +- .../resolver/utils/children_helper.test.ts | 4 +- .../routes/resolver/utils/children_helper.ts | 4 +- 6 files changed, 111 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts index fcea86be4ae9e..debe4a3da6a6f 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import _ from 'lodash'; import { EndpointDocGenerator, Event, @@ -79,9 +80,9 @@ describe('data generator', () => { const timestamp = new Date().getTime(); const processEvent = generator.generateEvent({ timestamp }); expect(processEvent['@timestamp']).toEqual(timestamp); - expect(processEvent.event.category).toEqual('process'); + expect(processEvent.event.category).toEqual(['process']); expect(processEvent.event.kind).toEqual('event'); - expect(processEvent.event.type).toEqual('start'); + expect(processEvent.event.type).toEqual(['start']); expect(processEvent.agent).not.toBeNull(); expect(processEvent.host).not.toBeNull(); expect(processEvent.process.entity_id).not.toBeNull(); @@ -94,7 +95,7 @@ describe('data generator', () => { expect(processEvent['@timestamp']).toEqual(timestamp); expect(processEvent.event.category).toEqual('dns'); expect(processEvent.event.kind).toEqual('event'); - expect(processEvent.event.type).toEqual('start'); + expect(processEvent.event.type).toEqual(['start']); expect(processEvent.agent).not.toBeNull(); expect(processEvent.host).not.toBeNull(); expect(processEvent.process.entity_id).not.toBeNull(); @@ -332,6 +333,12 @@ describe('data generator', () => { describe('creates alert ancestor tree', () => { let events: Event[]; + const isCategoryProcess = (event: Event) => { + return ( + _.isEqual(event.event.category, ['process']) || _.isEqual(event.event.category, 'process') + ); + }; + beforeEach(() => { events = generator.createAlertEventAncestry({ ancestors: 3, @@ -343,11 +350,7 @@ describe('data generator', () => { it('with n-1 process events', () => { for (let i = events.length - 2; i > 0; ) { const parentEntityIdOfChild = events[i].process.parent?.entity_id; - for ( - ; - --i >= -1 && (events[i].event.kind !== 'event' || events[i].event.category !== 'process'); - - ) { + for (; --i >= -1 && (events[i].event.kind !== 'event' || !isCategoryProcess(events[i])); ) { // related event - skip it } expect(i).toBeGreaterThanOrEqual(0); @@ -361,7 +364,7 @@ describe('data generator', () => { ; previousProcessEventIndex >= -1 && (events[previousProcessEventIndex].event.kind !== 'event' || - events[previousProcessEventIndex].event.category !== 'process'); + !isCategoryProcess(events[previousProcessEventIndex])); previousProcessEventIndex-- ) { // related event - skip it diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index 66e786cb02e63..97ac5c9030a3d 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -35,7 +35,7 @@ interface EventOptions { timestamp?: number; entityID?: string; parentEntityID?: string; - eventType?: string; + eventType?: string | string[]; eventCategory?: string | string[]; processName?: string; ancestry?: string[]; @@ -572,9 +572,9 @@ export class EndpointDocGenerator { }, ...detailRecordForEventType, event: { - category: options.eventCategory ? options.eventCategory : 'process', + category: options.eventCategory ? options.eventCategory : ['process'], kind: 'event', - type: options.eventType ? options.eventType : 'start', + type: options.eventType ? options.eventType : ['start'], id: this.seededUUIDv4(), }, host: this.commonInfo.host, @@ -633,7 +633,12 @@ export class EndpointDocGenerator { // place the event in the right array depending on its category if (event.event.kind === 'event') { - if (event.event.category === 'process') { + if ( + (Array.isArray(event.event.category) && + event.event.category.length === 1 && + event.event.category[0] === 'process') || + event.event.category === 'process' + ) { node.lifecycle.push(event); } else { node.relatedEvents.push(event); @@ -812,8 +817,8 @@ export class EndpointDocGenerator { timestamp: timestamp + termProcessDuration * 1000, entityID: root.process.entity_id, parentEntityID: root.process.parent?.entity_id, - eventCategory: 'process', - eventType: 'end', + eventCategory: ['process'], + eventType: ['end'], }) ); } @@ -838,8 +843,8 @@ export class EndpointDocGenerator { timestamp: timestamp + termProcessDuration * 1000, entityID: ancestor.process.entity_id, parentEntityID: ancestor.process.parent?.entity_id, - eventCategory: 'process', - eventType: 'end', + eventCategory: ['process'], + eventType: ['end'], ancestry: ancestor.process.Ext?.ancestry, ancestryArrayLimit: opts.ancestryArraySize, }) @@ -936,8 +941,8 @@ export class EndpointDocGenerator { timestamp: timestamp + processDuration * 1000, entityID: child.process.entity_id, parentEntityID: child.process.parent?.entity_id, - eventCategory: 'process', - eventType: 'end', + eventCategory: ['process'], + eventType: ['end'], ancestry: child.process.Ext?.ancestry, ancestryArrayLimit: opts.ancestryArraySize, }); diff --git a/x-pack/plugins/security_solution/common/endpoint/models/event.test.ts b/x-pack/plugins/security_solution/common/endpoint/models/event.test.ts index a0bf00f0274e6..62f923aa6d793 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/event.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/event.test.ts @@ -4,38 +4,90 @@ * you may not use this file except in compliance with the Elastic License. */ import { EndpointDocGenerator } from '../generate_data'; -import { descriptiveName } from './event'; +import { descriptiveName, isStart } from './event'; +import { ResolverEvent } from '../types'; -describe('Event descriptive names', () => { +describe('Generated documents', () => { let generator: EndpointDocGenerator; beforeEach(() => { generator = new EndpointDocGenerator('seed'); }); - it('returns the right name for a registry event', () => { - const extensions = { registry: { key: `HKLM/Windows/Software/abc` } }; - const event = generator.generateEvent({ eventCategory: 'registry', extensions }); - expect(descriptiveName(event)).toEqual({ subject: `HKLM/Windows/Software/abc` }); - }); + describe('Event descriptive names', () => { + it('returns the right name for a registry event', () => { + const extensions = { registry: { key: `HKLM/Windows/Software/abc` } }; + const event = generator.generateEvent({ eventCategory: 'registry', extensions }); + expect(descriptiveName(event)).toEqual({ subject: `HKLM/Windows/Software/abc` }); + }); - it('returns the right name for a network event', () => { - const randomIP = `${generator.randomIP()}`; - const extensions = { network: { direction: 'outbound', forwarded_ip: randomIP } }; - const event = generator.generateEvent({ eventCategory: 'network', extensions }); - expect(descriptiveName(event)).toEqual({ subject: `${randomIP}`, descriptor: 'outbound' }); - }); + it('returns the right name for a network event', () => { + const randomIP = `${generator.randomIP()}`; + const extensions = { network: { direction: 'outbound', forwarded_ip: randomIP } }; + const event = generator.generateEvent({ eventCategory: 'network', extensions }); + expect(descriptiveName(event)).toEqual({ subject: `${randomIP}`, descriptor: 'outbound' }); + }); - it('returns the right name for a file event', () => { - const extensions = { file: { path: 'C:\\My Documents\\business\\January\\processName' } }; - const event = generator.generateEvent({ eventCategory: 'file', extensions }); - expect(descriptiveName(event)).toEqual({ - subject: 'C:\\My Documents\\business\\January\\processName', + it('returns the right name for a file event', () => { + const extensions = { file: { path: 'C:\\My Documents\\business\\January\\processName' } }; + const event = generator.generateEvent({ eventCategory: 'file', extensions }); + expect(descriptiveName(event)).toEqual({ + subject: 'C:\\My Documents\\business\\January\\processName', + }); + }); + + it('returns the right name for a dns event', () => { + const extensions = { dns: { question: { name: `${generator.randomIP()}` } } }; + const event = generator.generateEvent({ eventCategory: 'dns', extensions }); + expect(descriptiveName(event)).toEqual({ subject: extensions.dns.question.name }); }); }); - it('returns the right name for a dns event', () => { - const extensions = { dns: { question: { name: `${generator.randomIP()}` } } }; - const event = generator.generateEvent({ eventCategory: 'dns', extensions }); - expect(descriptiveName(event)).toEqual({ subject: extensions.dns.question.name }); + describe('Start events', () => { + it('is a start event when event.type is a string', () => { + const event: ResolverEvent = generator.generateEvent({ + eventType: 'start', + }); + expect(isStart(event)).toBeTruthy(); + }); + + it('is a start event when event.type is an array of strings', () => { + const event: ResolverEvent = generator.generateEvent({ + eventType: ['start'], + }); + expect(isStart(event)).toBeTruthy(); + }); + + it('is a start event when event.type is an array of strings and contains start', () => { + let event: ResolverEvent = generator.generateEvent({ + eventType: ['bogus', 'start', 'creation'], + }); + expect(isStart(event)).toBeTruthy(); + + event = generator.generateEvent({ + eventType: ['start', 'bogus'], + }); + expect(isStart(event)).toBeTruthy(); + }); + + it('is not a start event when event.type is not start', () => { + const event: ResolverEvent = generator.generateEvent({ + eventType: ['end'], + }); + expect(isStart(event)).toBeFalsy(); + }); + + it('is not a start event when event.type is empty', () => { + const event: ResolverEvent = generator.generateEvent({ + eventType: [], + }); + expect(isStart(event)).toBeFalsy(); + }); + + it('is not a start event when event.type is bogus', () => { + const event: ResolverEvent = generator.generateEvent({ + eventType: ['bogus'], + }); + expect(isStart(event)).toBeFalsy(); + }); }); }); diff --git a/x-pack/plugins/security_solution/common/endpoint/models/event.ts b/x-pack/plugins/security_solution/common/endpoint/models/event.ts index f53da8fb1f096..216b5cc028588 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/event.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/event.ts @@ -9,10 +9,15 @@ export function isLegacyEvent(event: ResolverEvent): event is LegacyEndpointEven return (event as LegacyEndpointEvent).endgame !== undefined; } -export function isProcessStart(event: ResolverEvent): boolean { +export function isStart(event: ResolverEvent): boolean { if (isLegacyEvent(event)) { return event.event?.type === 'process_start' || event.event?.action === 'fork_event'; } + + if (Array.isArray(event.event.type)) { + return event.event.type.includes('start'); + } + return event.event.type === 'start'; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.test.ts index ca5b5aef0f651..01dd59b2611d9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.test.ts @@ -10,12 +10,12 @@ import { TreeNode, } from '../../../../../common/endpoint/generate_data'; import { ChildrenNodesHelper } from './children_helper'; -import { eventId, isProcessStart } from '../../../../../common/endpoint/models/event'; +import { eventId, isStart } from '../../../../../common/endpoint/models/event'; function getStartEvents(events: Event[]): Event[] { const startEvents: Event[] = []; for (const event of events) { - if (isProcessStart(event)) { + if (isStart(event)) { startEvents.push(event); } } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts index 01e356682ac47..d3ca7a54c16d2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts @@ -7,7 +7,7 @@ import { entityId, parentEntityId, - isProcessStart, + isStart, getAncestryAsArray, } from '../../../../../common/endpoint/models/event'; import { @@ -99,7 +99,7 @@ export class ChildrenNodesHelper { for (const event of startEvents) { const parentID = parentEntityId(event); const entityID = entityId(event); - if (parentID && entityID && isProcessStart(event)) { + if (parentID && entityID && isStart(event)) { // don't actually add the start event to the node, because that'll be done in // a different call const childNode = this.getOrCreateChildNode(entityID); From 5e8e01fd0f3ea5516a2f68bef9e7a9877b9e549a Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Tue, 28 Jul 2020 15:00:41 +0100 Subject: [PATCH 35/75] removed ESO migration from alerting (#73420) This PR removes the use of ESO migration from alerting as we do not actually need this until the RBAC work lands, which should be 7.10. This allows us to concentrate the challenges of introducing RBAC into one single release which hopefully will help us better mitigate potential regressions. --- .../alerts/server/saved_objects/index.ts | 2 - .../server/saved_objects/migrations.test.ts | 121 ------------------ .../alerts/server/saved_objects/migrations.ts | 53 -------- .../spaces_only/tests/alerting/index.ts | 3 - .../spaces_only/tests/alerting/migrations.ts | 43 ------- 5 files changed, 222 deletions(-) delete mode 100644 x-pack/plugins/alerts/server/saved_objects/migrations.test.ts delete mode 100644 x-pack/plugins/alerts/server/saved_objects/migrations.ts delete mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts diff --git a/x-pack/plugins/alerts/server/saved_objects/index.ts b/x-pack/plugins/alerts/server/saved_objects/index.ts index 06ce8d673e6b7..c98d9bcbd9ae5 100644 --- a/x-pack/plugins/alerts/server/saved_objects/index.ts +++ b/x-pack/plugins/alerts/server/saved_objects/index.ts @@ -6,7 +6,6 @@ import { SavedObjectsServiceSetup } from 'kibana/server'; import mappings from './mappings.json'; -import { getMigrations } from './migrations'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; export function setupSavedObjects( @@ -17,7 +16,6 @@ export function setupSavedObjects( name: 'alert', hidden: true, namespaceType: 'single', - migrations: getMigrations(encryptedSavedObjects), mappings: mappings.alert, }); diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts deleted file mode 100644 index 19f4e918b7862..0000000000000 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import uuid from 'uuid'; -import { getMigrations } from './migrations'; -import { RawAlert } from '../types'; -import { SavedObjectUnsanitizedDoc } from 'kibana/server'; -import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; -import { migrationMocks } from 'src/core/server/mocks'; - -const { log } = migrationMocks.createContext(); -const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); - -describe('7.9.0', () => { - beforeEach(() => { - jest.resetAllMocks(); - encryptedSavedObjectsSetup.createMigration.mockImplementation( - (shouldMigrateWhenPredicate, migration) => migration - ); - }); - - test('changes nothing on alerts by other plugins', () => { - const migration790 = getMigrations(encryptedSavedObjectsSetup)['7.9.0']; - const alert = getMockData({}); - expect(migration790(alert, { log })).toMatchObject(alert); - - expect(encryptedSavedObjectsSetup.createMigration).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function) - ); - }); - - test('migrates the consumer for alerting', () => { - const migration790 = getMigrations(encryptedSavedObjectsSetup)['7.9.0']; - const alert = getMockData({ - consumer: 'alerting', - }); - expect(migration790(alert, { log })).toMatchObject({ - ...alert, - attributes: { - ...alert.attributes, - consumer: 'alerts', - }, - }); - }); -}); - -describe('7.10.0', () => { - beforeEach(() => { - jest.resetAllMocks(); - encryptedSavedObjectsSetup.createMigration.mockImplementation( - (shouldMigrateWhenPredicate, migration) => migration - ); - }); - - test('changes nothing on alerts by other plugins', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0']; - const alert = getMockData({}); - expect(migration710(alert, { log })).toMatchObject(alert); - - expect(encryptedSavedObjectsSetup.createMigration).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function) - ); - }); - - test('migrates the consumer for metrics', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0']; - const alert = getMockData({ - consumer: 'metrics', - }); - expect(migration710(alert, { log })).toMatchObject({ - ...alert, - attributes: { - ...alert.attributes, - consumer: 'infrastructure', - }, - }); - }); -}); - -function getMockData( - overwrites: Record = {} -): SavedObjectUnsanitizedDoc { - return { - attributes: { - enabled: true, - name: 'abc', - tags: ['foo'], - alertTypeId: '123', - consumer: 'bar', - apiKey: '', - apiKeyOwner: '', - schedule: { interval: '10s' }, - throttle: null, - params: { - bar: true, - }, - muteAll: false, - mutedInstanceIds: [], - createdBy: new Date().toISOString(), - updatedBy: new Date().toISOString(), - createdAt: new Date().toISOString(), - actions: [ - { - group: 'default', - actionRef: '1', - actionTypeId: '1', - params: { - foo: true, - }, - }, - ], - ...overwrites, - }, - id: uuid.v4(), - type: 'alert', - }; -} diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.ts deleted file mode 100644 index 57a4005887093..0000000000000 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { - SavedObjectMigrationMap, - SavedObjectUnsanitizedDoc, - SavedObjectMigrationFn, -} from '../../../../../src/core/server'; -import { RawAlert } from '../types'; -import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; - -export function getMigrations( - encryptedSavedObjects: EncryptedSavedObjectsPluginSetup -): SavedObjectMigrationMap { - return { - /** - * In v7.9.0 we changed the Alerting plugin so it uses the `consumer` value of `alerts` - * prior to that we were using `alerting` and we need to keep these in sync - */ - '7.9.0': changeAlertingConsumer(encryptedSavedObjects, 'alerting', 'alerts'), - /** - * In v7.10.0 we changed the Matrics plugin so it uses the `consumer` value of `infrastructure` - * prior to that we were using `metrics` and we need to keep these in sync - */ - '7.10.0': changeAlertingConsumer(encryptedSavedObjects, 'metrics', 'infrastructure'), - }; -} - -function changeAlertingConsumer( - encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, - from: string, - to: string -): SavedObjectMigrationFn { - return encryptedSavedObjects.createMigration( - function shouldBeMigrated(doc): doc is SavedObjectUnsanitizedDoc { - return doc.attributes.consumer === from; - }, - (doc: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc => { - const { - attributes: { consumer }, - } = doc; - return { - ...doc, - attributes: { - ...doc.attributes, - consumer: consumer === from ? to : consumer, - }, - }; - } - ); -} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts index 0970738b630c4..a23f0fa835313 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts @@ -27,8 +27,5 @@ export default function alertingTests({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./alerts_space1')); loadTestFile(require.resolve('./alerts_default_space')); loadTestFile(require.resolve('./builtin_alert_types')); - - // note that this test will destroy existing spaces - loadTestFile(require.resolve('./migrations')); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts deleted file mode 100644 index d0e1be12e762f..0000000000000 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { getUrlPrefix } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function createGetTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('migrations', () => { - before(async () => { - await esArchiver.load('alerts'); - }); - - after(async () => { - await esArchiver.unload('alerts'); - }); - - it('7.9.0 migrates the `alerting` consumer to be the `alerts`', async () => { - const response = await supertest.get( - `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` - ); - - expect(response.status).to.eql(200); - expect(response.body.consumer).to.equal('alerts'); - }); - - it('7.10.0 migrates the `metrics` consumer to be the `infrastructure`', async () => { - const response = await supertest.get( - `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-fdf248d5f2a4` - ); - - expect(response.status).to.eql(200); - expect(response.body.consumer).to.equal('infrastructure'); - }); - }); -} From dca4a2359724e9121df0b919f57ef2fb14bada4c Mon Sep 17 00:00:00 2001 From: Andrew Goldstein Date: Tue, 28 Jul 2020 08:09:35 -0600 Subject: [PATCH 36/75] [Security Solution] Full screen fixes for Timeline based views (#73421) ## Full screen fixes for Timeline based views - Fixes an issue where sometimes, Global navigation is hidden until the page is scrolled when exiting full screen mode - Improves performance by adding an intent delay before showing the draggable wrapper hover menu - Removes an unnecessary CSS transition ### Sometimes, Global navigation is hidden until the page is scrolled when exiting full screen mode Sometimes, after exiting `Full screen` mode in a page, for example, the `Detections` page, the global navigation, e.g. `Overview Detections Hosts...` is hidden until the page is scrolled. To reproduce: 1) Navigate to the `Detections` page 2) Click the `Full screen` button in the table 3) Without scrolling the full screen view, click the `Exit full screen` button **Expected result** - [x] The global navigation e.g. `Overview Detections Hosts...` is visible above the search bar, per the screenshot below: ![correct-global-navigation](https://user-images.githubusercontent.com/4459398/87717870-571bef80-c76e-11ea-8b7b-1850094326b3.png) 4) Once again, click the `Full screen` button in the table 5) This time, expand an event, which will scroll the view 6) Once again, click the `Exit full screen` button **Expected result** - [x] The global navigation e.g. `Overview Detections Hosts...` is visible above the search bar **Actual result** - [ ] Sometimes, the global navigation e.g. `Overview Detections Hosts...` is **not** visible until the page is scrolled --- .../security_solution/common/constants.ts | 1 + .../index.test.tsx | 3 + .../drag_and_drop/draggable_wrapper.test.tsx | 6 + .../drag_and_drop/provider_container.tsx | 7 -- .../filters_global/filters_global.test.tsx | 104 +++++++++++++++++- .../filters_global/filters_global.tsx | 29 +++-- .../public/common/components/page/index.tsx | 13 ++- .../components/with_hover_actions/index.tsx | 30 +++-- .../containers/use_full_screen/index.tsx | 12 +- .../detection_engine/detection_engine.tsx | 5 +- .../detection_engine/rules/details/index.tsx | 5 +- .../public/hosts/pages/details/index.tsx | 5 +- .../public/hosts/pages/hosts.tsx | 5 +- .../public/network/pages/ip_details/index.tsx | 2 +- .../public/network/pages/network.tsx | 5 +- .../public/overview/pages/overview.tsx | 2 +- .../fields_browser/field_name.test.tsx | 6 + 17 files changed, 206 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index f934d90c740a5..c74cf888a2db6 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -32,6 +32,7 @@ export const DEFAULT_INTERVAL_PAUSE = true; export const DEFAULT_INTERVAL_TYPE = 'manual'; export const DEFAULT_INTERVAL_VALUE = 300000; // ms export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges'; +export const SCROLLING_DISABLED_CLASS_NAME = 'scrolling-disabled'; export const FILTERS_GLOBAL_HEIGHT = 109; // px export const FULL_SCREEN_TOGGLED_CLASS_NAME = 'fullScreenToggled'; export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51'; diff --git a/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.test.tsx index 2af6569394e8f..eced73e9c3d67 100644 --- a/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.test.tsx @@ -45,6 +45,7 @@ describe('AddFilterToGlobalSearchBar Component', () => { ); beforeEach(() => { + jest.useFakeTimers(); store = createStore( state, SUB_PLUGINS_REDUCER, @@ -159,6 +160,8 @@ describe('AddFilterToGlobalSearchBar Component', () => { wrapper.find('[data-test-subj="withHoverActionsButton"]').simulate('mouseenter'); wrapper.update(); + jest.runAllTimers(); + wrapper.update(); wrapper .find('[data-test-subj="hover-actions-container"] [data-euiicon-type]') diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx index e17fc7b9ef9bd..ebfa9ac22bdc7 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx @@ -22,6 +22,10 @@ describe('DraggableWrapper', () => { const message = 'draggable wrapper content'; const mount = useMountAppended(); + beforeEach(() => { + jest.useFakeTimers(); + }); + describe('rendering', () => { test('it renders against the snapshot', () => { const wrapper = shallow( @@ -78,6 +82,8 @@ describe('DraggableWrapper', () => { wrapper.find('[data-test-subj="withHoverActionsButton"]').simulate('mouseenter'); wrapper.update(); + jest.runAllTimers(); + wrapper.update(); expect(wrapper.find('[data-test-subj="copy-to-clipboard"]').exists()).toBe(true); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/provider_container.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/provider_container.tsx index 06cb8ee2e1a46..8db6d073f9687 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/provider_container.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/provider_container.tsx @@ -13,13 +13,6 @@ interface ProviderContainerProps { } const ProviderContainerComponent = styled.div` - &, - &::before, - &::after { - transition: background ${({ theme }) => theme.eui.euiAnimSpeedFast} ease, - color ${({ theme }) => theme.eui.euiAnimSpeedFast} ease; - } - ${({ isDragging }) => !isDragging && css` diff --git a/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.test.tsx b/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.test.tsx index ffac0496e9f1a..9fda60b1f289d 100644 --- a/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.test.tsx @@ -4,20 +4,120 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallow } from 'enzyme'; +import { mount, ReactWrapper, shallow } from 'enzyme'; import React from 'react'; +import { StickyContainer } from 'react-sticky'; import '../../mock/match_media'; import { FiltersGlobal } from './filters_global'; +import { TestProviders } from '../../mock/test_providers'; describe('rendering', () => { test('renders correctly', () => { const wrapper = shallow( - +

      {'Additional filters here.'}

      ); expect(wrapper).toMatchSnapshot(); }); + + describe('full screen mode', () => { + let wrapper: ReactWrapper; + + beforeEach(() => { + wrapper = mount( + + + +

      {'Filter content'}

      +
      +
      +
      + ); + }); + + test('it does NOT render the sticky container', () => { + expect(wrapper.find('[data-test-subj="sticky-filters-global-container"]').exists()).toBe( + false + ); + }); + + test('it renders the non-sticky container', () => { + expect(wrapper.find('[data-test-subj="non-sticky-global-container"]').exists()).toBe(true); + }); + + test('it does NOT render the container with a `display: none` style when `show` is true (the default)', () => { + expect( + wrapper.find('[data-test-subj="non-sticky-global-container"]').first() + ).not.toHaveStyleRule('display', 'none'); + }); + }); + + describe('non-full screen mode', () => { + let wrapper: ReactWrapper; + + beforeEach(() => { + wrapper = mount( + + + +

      {'Filter content'}

      +
      +
      +
      + ); + }); + + test('it renders the sticky container', () => { + expect(wrapper.find('[data-test-subj="sticky-filters-global-container"]').exists()).toBe( + true + ); + }); + + test('it does NOT render the non-sticky container', () => { + expect(wrapper.find('[data-test-subj="non-sticky-global-container"]').exists()).toBe(false); + }); + + test('it does NOT render the container with a `display: none` style when `show` is true (the default)', () => { + expect( + wrapper.find('[data-test-subj="sticky-filters-global-container"]').first() + ).not.toHaveStyleRule('display', 'none'); + }); + }); + + describe('when show is false', () => { + test('in full screen mode it renders the container with a `display: none` style', () => { + const wrapper = mount( + + + +

      {'Filter content'}

      +
      +
      +
      + ); + + expect( + wrapper.find('[data-test-subj="non-sticky-global-container"]').first() + ).toHaveStyleRule('display', 'none'); + }); + + test('in non-full screen mode it renders the container with a `display: none` style', () => { + const wrapper = mount( + + + +

      {'Filter content'}

      +
      +
      +
      + ); + + expect( + wrapper.find('[data-test-subj="sticky-filters-global-container"]').first() + ).toHaveStyleRule('display', 'none'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx b/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx index b52438486406e..80e7209492fa5 100644 --- a/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx +++ b/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx @@ -47,20 +47,33 @@ const FiltersGlobalContainer = styled.header<{ show: boolean }>` FiltersGlobalContainer.displayName = 'FiltersGlobalContainer'; +const NO_STYLE: React.CSSProperties = {}; + export interface FiltersGlobalProps { children: React.ReactNode; + globalFullScreen: boolean; show?: boolean; } -export const FiltersGlobal = React.memo(({ children, show = true }) => ( - - {({ style, isSticky }) => ( - - +export const FiltersGlobal = React.memo( + ({ children, globalFullScreen, show = true }) => + globalFullScreen ? ( + + {children} - )} - -)); + ) : ( + + {({ style, isSticky }) => ( + + + {children} + + + )} + + ) +); + FiltersGlobal.displayName = 'FiltersGlobal'; diff --git a/x-pack/plugins/security_solution/public/common/components/page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page/index.tsx index 8737fa95c94a2..8bf0690bfd0ad 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/index.tsx @@ -7,7 +7,10 @@ import { EuiBadge, EuiDescriptionList, EuiFlexGroup, EuiIcon, EuiPage } from '@elastic/eui'; import styled, { createGlobalStyle } from 'styled-components'; -import { FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../common/constants'; +import { + FULL_SCREEN_TOGGLED_CLASS_NAME, + SCROLLING_DISABLED_CLASS_NAME, +} from '../../../../common/constants'; /* SIDE EFFECT: the following `createGlobalStyle` overrides default styling in angular code that was not theme-friendly @@ -63,6 +66,14 @@ export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimar .${FULL_SCREEN_TOGGLED_CLASS_NAME} { ${({ theme }) => `background-color: ${theme.eui.euiColorPrimary} !important`}; } + + .${SCROLLING_DISABLED_CLASS_NAME} body { + overflow-y: hidden; + } + + .${SCROLLING_DISABLED_CLASS_NAME} #kibana-body { + overflow-y: hidden; + } `; export const DescriptionListStyled = styled(EuiDescriptionList)` diff --git a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx index 9e28345ffbbcf..b4abdd4b91805 100644 --- a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx @@ -10,6 +10,11 @@ import styled from 'styled-components'; import { IS_DRAGGING_CLASS_NAME } from '../drag_and_drop/helpers'; +/** + * To avoid expensive changes to the DOM, delay showing the popover menu + */ +const HOVER_INTENT_DELAY = 100; // ms + // eslint-disable-next-line @typescript-eslint/no-explicit-any const WithHoverActionsPopover = (styled(EuiPopover as any)` .euiPopover__anchor { @@ -51,18 +56,27 @@ export const WithHoverActions = React.memo( ({ alwaysShow = false, closePopOverTrigger, hoverContent, render }) => { const [isOpen, setIsOpen] = useState(hoverContent != null && alwaysShow); const [showHoverContent, setShowHoverContent] = useState(false); + const [hoverTimeout, setHoverTimeout] = useState(undefined); + const onMouseEnter = useCallback(() => { - // NOTE: the following read from the DOM is expensive, but not as - // expensive as the default behavior, which adds a div to the body, - // which-in turn performs a more expensive change to the layout - if (!document.body.classList.contains(IS_DRAGGING_CLASS_NAME)) { - setShowHoverContent(true); - } - }, []); + setHoverTimeout( + Number( + setTimeout(() => { + // NOTE: the following read from the DOM is expensive, but not as + // expensive as the default behavior, which adds a div to the body, + // which-in turn performs a more expensive change to the layout + if (!document.body.classList.contains(IS_DRAGGING_CLASS_NAME)) { + setShowHoverContent(true); + } + }, HOVER_INTENT_DELAY) + ) + ); + }, [setHoverTimeout, setShowHoverContent]); const onMouseLeave = useCallback(() => { + clearTimeout(hoverTimeout); setShowHoverContent(false); - }, []); + }, [hoverTimeout, setShowHoverContent]); const content = useMemo( () => ( diff --git a/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx index b8050034d34a6..aa0d90a216035 100644 --- a/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx @@ -6,6 +6,7 @@ import { useCallback, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { SCROLLING_DISABLED_CLASS_NAME } from '../../../../common/constants'; import { inputsSelectors } from '../../store'; import { inputsActions } from '../../store/actions'; @@ -16,7 +17,16 @@ export const useFullScreen = () => { const timelineFullScreen = useSelector(inputsSelectors.timelineFullScreenSelector) ?? false; const setGlobalFullScreen = useCallback( - (fullScreen: boolean) => dispatch(inputsActions.setFullScreen({ id: 'global', fullScreen })), + (fullScreen: boolean) => { + if (fullScreen) { + document.body.classList.add(SCROLLING_DISABLED_CLASS_NAME); + } else { + document.body.classList.remove(SCROLLING_DISABLED_CLASS_NAME); + setTimeout(() => window.scrollTo(0, 0), 0); + } + + dispatch(inputsActions.setFullScreen({ id: 'global', fullScreen })); + }, [dispatch] ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 090fdc4980933..8385fcc71682d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -156,7 +156,10 @@ export const DetectionEnginePageComponent: React.FC = ({ {indicesExist ? ( - + diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 9c130a7d351fa..90424e1fb9dd0 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -366,7 +366,10 @@ export const RuleDetailsPageComponent: FC = ({ {indicesExist ? ( - + diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index 781aa711ff0d9..b6c1727ee6afa 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -104,7 +104,10 @@ const HostDetailsComponent = React.memo( {indicesExist ? ( - + diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index 1219effa5ff6d..1d0b73f80a69a 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -98,7 +98,10 @@ export const HostsComponent = React.memo( {indicesExist ? ( - + diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx index e06f5489a3fc2..42469a4bf29da 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx @@ -90,7 +90,7 @@ export const IPDetailsComponent: React.FC {indicesExist ? ( - + diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index ca8da4eb711e5..f516f2a2de346 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -106,7 +106,10 @@ const NetworkComponent = React.memo( {indicesExist ? ( - + diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 6563f3c2b824d..423aa597a0129 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -71,7 +71,7 @@ const OverviewComponent: React.FC = ({ <> {indicesExist ? ( - + diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx index ddd5c6f07e8b5..2e48215a89473 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx @@ -28,6 +28,10 @@ const defaultProps = { }; describe('FieldName', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + test('it renders the field name', () => { const wrapper = mount( @@ -48,6 +52,8 @@ describe('FieldName', () => { ); wrapper.find('[data-test-subj="withHoverActionsButton"]').at(0).simulate('mouseenter'); wrapper.update(); + jest.runAllTimers(); + wrapper.update(); expect(wrapper.find('[data-test-subj="copy-to-clipboard"]').exists()).toBe(true); }); From f4104743e388c9cec6f91d153bfc4145f541cabb Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Tue, 28 Jul 2020 15:20:24 +0100 Subject: [PATCH 37/75] [Alerting] Control Alerts Management via feature controls & privileges (#72029) This PR removes the alerting and actions ui privileges (alerting:show, actions:show, etc...) and instead relies on the standard Kibana feature control model to decide whether management displays the Alerts Management section under management. --- examples/alerting_example/server/plugin.ts | 13 ++++- x-pack/plugins/actions/server/feature.ts | 9 ++++ .../alerting_builtins/server/feature.ts | 13 ++++- x-pack/plugins/alerts/server/plugin.ts | 10 ++++ x-pack/plugins/apm/server/feature.ts | 9 ++++ x-pack/plugins/infra/server/features.ts | 13 ++++- .../security_solution/server/plugin.ts | 13 ++++- .../public/application/app.tsx | 18 +++---- .../public/application/home.tsx | 40 ++++++-------- .../public/application/lib/capabilities.ts | 13 ----- .../components/alert_details.test.tsx | 3 -- .../sections/alert_form/alert_form.tsx | 22 ++++---- .../components/alerts_list.test.tsx | 28 ++-------- .../alerts_list/components/alerts_list.tsx | 53 ++++++++++--------- .../triggers_actions_ui/public/plugin.ts | 26 +++------ x-pack/plugins/uptime/server/kibana.index.ts | 13 ++++- 16 files changed, 157 insertions(+), 139 deletions(-) diff --git a/examples/alerting_example/server/plugin.ts b/examples/alerting_example/server/plugin.ts index 49352cc285693..e74cad28f77f4 100644 --- a/examples/alerting_example/server/plugin.ts +++ b/examples/alerting_example/server/plugin.ts @@ -44,6 +44,9 @@ export class AlertingExamplePlugin implements Plugin { + return { + management: { + insightsAndAlerting: { + triggersActions: true, + }, + }, + }; + }); + this.isESOUsingEphemeralEncryptionKey = plugins.encryptedSavedObjects.usingEphemeralEncryptionKey; diff --git a/x-pack/plugins/apm/server/feature.ts b/x-pack/plugins/apm/server/feature.ts index 38e75f75ad04b..971bc96234376 100644 --- a/x-pack/plugins/apm/server/feature.ts +++ b/x-pack/plugins/apm/server/feature.ts @@ -17,6 +17,9 @@ export const APM_FEATURE = { navLinkId: 'apm', app: ['apm', 'kibana'], catalogue: ['apm'], + management: { + insightsAndAlerting: ['triggersActions'], + }, alerting: Object.values(AlertType), // see x-pack/plugins/features/common/feature_kibana_privileges.ts privileges: { @@ -31,6 +34,9 @@ export const APM_FEATURE = { alerting: { all: Object.values(AlertType), }, + management: { + insightsAndAlerting: ['triggersActions'], + }, ui: ['show', 'save', 'alerting:show', 'alerting:save'], }, read: { @@ -44,6 +50,9 @@ export const APM_FEATURE = { alerting: { all: Object.values(AlertType), }, + management: { + insightsAndAlerting: ['triggersActions'], + }, ui: ['show', 'alerting:show', 'alerting:save'], }, }, diff --git a/x-pack/plugins/infra/server/features.ts b/x-pack/plugins/infra/server/features.ts index fdbd1ec894022..3e32cebf19ac2 100644 --- a/x-pack/plugins/infra/server/features.ts +++ b/x-pack/plugins/infra/server/features.ts @@ -19,6 +19,9 @@ export const METRICS_FEATURE = { navLinkId: 'metrics', app: ['infra', 'metrics', 'kibana'], catalogue: ['infraops'], + management: { + insightsAndAlerting: ['triggersActions'], + }, alerting: [METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID], privileges: { all: { @@ -32,7 +35,10 @@ export const METRICS_FEATURE = { alerting: { all: [METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID], }, - ui: ['show', 'configureSource', 'save', 'alerting:show'], + management: { + insightsAndAlerting: ['triggersActions'], + }, + ui: ['show', 'configureSource', 'save'], }, read: { app: ['infra', 'metrics', 'kibana'], @@ -45,7 +51,10 @@ export const METRICS_FEATURE = { alerting: { all: [METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID], }, - ui: ['show', 'alerting:show'], + management: { + insightsAndAlerting: ['triggersActions'], + }, + ui: ['show'], }, }, }; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 06cd3138ca564..8fc413236dd2c 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -174,6 +174,9 @@ export class Plugin implements IPlugin { }; export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => { - const { capabilities } = useAppDependencies(); - const canShowAlerts = hasShowAlertsCapability(capabilities); - const DEFAULT_SECTION: Section = canShowAlerts ? 'alerts' : 'connectors'; return ( - {canShowAlerts && ( - - )} - + + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index eeb8a77717333..15099242b6e17 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -25,7 +25,7 @@ import { Section, routeToConnectors, routeToAlerts } from './constants'; import { getCurrentBreadcrumb } from './lib/breadcrumb'; import { getCurrentDocTitle } from './lib/doc_title'; import { useAppDependencies } from './app_context'; -import { hasShowActionsCapability, hasShowAlertsCapability } from './lib/capabilities'; +import { hasShowActionsCapability } from './lib/capabilities'; import { ActionsConnectorsList } from './sections/actions_connectors_list/components/actions_connectors_list'; import { AlertsList } from './sections/alerts_list/components/alerts_list'; @@ -45,23 +45,17 @@ export const TriggersActionsUIHome: React.FunctionComponent = []; - if (canShowAlerts) { - tabs.push({ - id: 'alerts', - name: ( - - ), - }); - } + tabs.push({ + id: 'alerts', + name: ( + + ), + }); if (canShowActions) { tabs.push({ @@ -151,17 +145,15 @@ export const TriggersActionsUIHome: React.FunctionComponent )} - {canShowAlerts && ( - ( - - - - )} - /> - )} + ( + + + + )} + /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts index 065a782ee96a2..9e89a38377a4d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BUILT_IN_ALERTS_FEATURE_ID } from '../../../../alerting_builtins/common'; import { Alert, AlertType } from '../../types'; /** @@ -15,18 +14,6 @@ import { Alert, AlertType } from '../../types'; type Capabilities = Record; -const apps = ['apm', 'siem', 'uptime', 'infrastructure', 'actions', BUILT_IN_ALERTS_FEATURE_ID]; - -function hasCapability(capabilities: Capabilities, capability: string) { - return apps.some((app) => capabilities[app]?.[capability]); -} - -function createCapabilityCheck(capability: string) { - return (capabilities: Capabilities) => hasCapability(capabilities, capability); -} - -export const hasShowAlertsCapability = createCapabilityCheck('alerting:show'); - export const hasShowActionsCapability = (capabilities: Capabilities) => capabilities?.actions?.show; export const hasSaveActionsCapability = (capabilities: Capabilities) => capabilities?.actions?.save; export const hasExecuteActionsCapability = (capabilities: Capabilities) => diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index a620a0db45408..16d1a5c7c9c65 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -29,9 +29,6 @@ jest.mock('../../../app_context', () => ({ http: jest.fn(), capabilities: { get: jest.fn(() => ({})), - securitySolution: { - 'alerting:show': true, - }, }, actionTypeRegistry: jest.fn(), alertTypeRegistry: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 9d54baf359af5..c0674e6c4a5f7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -244,15 +244,7 @@ export const AlertForm = ({ ) : null} {AlertParamsExpressionComponent ? ( - - - - - - } - > + - ) : ( + ) : alertTypesIndex ? ( + ) : ( + )} ); }; +const CenterJustifiedSpinner = () => ( + + + + + +); + const NoAuthorizedAlertTypes = ({ operation }: { operation: string }) => ( { http: mockes.http, uiSettings: mockes.uiSettings, navigateToApp, - capabilities: { - ...capabilities, - securitySolution: { - 'alerting:show': true, - }, - }, + capabilities, history: scopedHistoryMock.create(), setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, @@ -223,12 +218,7 @@ describe('alerts_list component with items', () => { http: mockes.http, uiSettings: mockes.uiSettings, navigateToApp, - capabilities: { - ...capabilities, - securitySolution: { - 'alerting:show': true, - }, - }, + capabilities, history: scopedHistoryMock.create(), setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, @@ -303,12 +293,7 @@ describe('alerts_list component empty with show only capability', () => { http: mockes.http, uiSettings: mockes.uiSettings, navigateToApp, - capabilities: { - ...capabilities, - securitySolution: { - 'alerting:show': true, - }, - }, + capabilities, history: scopedHistoryMock.create(), setBreadcrumbs: jest.fn(), actionTypeRegistry: { @@ -417,12 +402,7 @@ describe('alerts_list with show only capability', () => { http: mockes.http, uiSettings: mockes.uiSettings, navigateToApp, - capabilities: { - ...capabilities, - securitySolution: { - 'alerting:show': true, - }, - }, + capabilities, history: scopedHistoryMock.create(), setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 8cb7afbda0e70..2b2897a2181b1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -93,7 +93,7 @@ export const AlertsList: React.FunctionComponent = () => { useEffect(() => { loadAlertsData(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [page, searchText, typesFilter, actionTypesFilter]); + }, [alertTypesState, page, searchText, typesFilter, actionTypesFilter]); useEffect(() => { (async () => { @@ -136,30 +136,33 @@ export const AlertsList: React.FunctionComponent = () => { }, []); async function loadAlertsData() { - setAlertsState({ ...alertsState, isLoading: true }); - try { - const alertsResponse = await loadAlerts({ - http, - page, - searchText, - typesFilter, - actionTypesFilter, - }); - setAlertsState({ - isLoading: false, - data: alertsResponse.data, - totalItemCount: alertsResponse.total, - }); - } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate( - 'xpack.triggersActionsUI.sections.alertsList.unableToLoadAlertsMessage', - { - defaultMessage: 'Unable to load alerts', - } - ), - }); - setAlertsState({ ...alertsState, isLoading: false }); + const hasAnyAuthorizedAlertType = alertTypesState.data.size > 0; + if (hasAnyAuthorizedAlertType) { + setAlertsState({ ...alertsState, isLoading: true }); + try { + const alertsResponse = await loadAlerts({ + http, + page, + searchText, + typesFilter, + actionTypesFilter, + }); + setAlertsState({ + isLoading: false, + data: alertsResponse.data, + totalItemCount: alertsResponse.total, + }); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.unableToLoadAlertsMessage', + { + defaultMessage: 'Unable to load alerts', + } + ), + }); + setAlertsState({ ...alertsState, isLoading: false }); + } } } diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index af4d2784cfa67..25a917c7a1a15 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -14,13 +14,11 @@ import { import { i18n } from '@kbn/i18n'; import { registerBuiltInActionTypes } from './application/components/builtin_action_types'; import { registerBuiltInAlertTypes } from './application/components/builtin_alert_types'; -import { hasShowActionsCapability, hasShowAlertsCapability } from './application/lib/capabilities'; import { ActionTypeModel, AlertTypeModel } from './types'; import { TypeRegistry } from './application/type_registry'; import { ManagementSetup, ManagementAppMountParams, - ManagementApp, } from '../../../../src/plugins/management/public'; import { boot } from './application/boot'; import { ChartsPluginStart } from '../../../../src/plugins/charts/public'; @@ -50,10 +48,14 @@ interface PluginsStart { export class Plugin implements - CorePlugin { + CorePlugin< + TriggersAndActionsUIPublicPluginSetup, + TriggersAndActionsUIPublicPluginStart, + PluginsSetup, + PluginsStart + > { private actionTypeRegistry: TypeRegistry; private alertTypeRegistry: TypeRegistry; - private managementApp?: ManagementApp; constructor(initializerContext: PluginInitializerContext) { const actionTypeRegistry = new TypeRegistry(); @@ -67,7 +69,7 @@ export class Plugin const actionTypeRegistry = this.actionTypeRegistry; const alertTypeRegistry = this.alertTypeRegistry; - this.managementApp = plugins.management.sections.section.insightsAndAlerting.registerApp({ + plugins.management.sections.section.insightsAndAlerting.registerApp({ id: 'triggersActions', title: i18n.translate('xpack.triggersActionsUI.managementSection.displayName', { defaultMessage: 'Alerts and Actions', @@ -116,19 +118,7 @@ export class Plugin }; } - public start(core: CoreStart): TriggersAndActionsUIPublicPluginStart { - const { capabilities } = core.application; - - const canShowActions = hasShowActionsCapability(capabilities); - const canShowAlerts = hasShowAlertsCapability(capabilities); - const managementApp = this.managementApp as ManagementApp; - - // Don't register routes when user doesn't have access to the application - if (canShowActions || canShowAlerts) { - managementApp.enable(); - } else { - managementApp.disable(); - } + public start(): TriggersAndActionsUIPublicPluginStart { return { actionTypeRegistry: this.actionTypeRegistry, alertTypeRegistry: this.alertTypeRegistry, diff --git a/x-pack/plugins/uptime/server/kibana.index.ts b/x-pack/plugins/uptime/server/kibana.index.ts index a2d5f58bbec14..2bf0d84a49de1 100644 --- a/x-pack/plugins/uptime/server/kibana.index.ts +++ b/x-pack/plugins/uptime/server/kibana.index.ts @@ -35,6 +35,9 @@ export const initServerWithKibana = (server: UptimeCoreSetup, plugins: UptimeCor icon: 'uptimeApp', app: ['uptime', 'kibana'], catalogue: ['uptime'], + management: { + insightsAndAlerting: ['triggersActions'], + }, alerting: ['xpack.uptime.alerts.tls', 'xpack.uptime.alerts.monitorStatus'], privileges: { all: { @@ -48,7 +51,10 @@ export const initServerWithKibana = (server: UptimeCoreSetup, plugins: UptimeCor alerting: { all: ['xpack.uptime.alerts.tls', 'xpack.uptime.alerts.monitorStatus'], }, - ui: ['save', 'configureSettings', 'show', 'alerting:show'], + management: { + insightsAndAlerting: ['triggersActions'], + }, + ui: ['save', 'configureSettings', 'show'], }, read: { app: ['uptime', 'kibana'], @@ -61,7 +67,10 @@ export const initServerWithKibana = (server: UptimeCoreSetup, plugins: UptimeCor alerting: { all: ['xpack.uptime.alerts.tls', 'xpack.uptime.alerts.monitorStatus'], }, - ui: ['show', 'alerting:show'], + management: { + insightsAndAlerting: ['triggersActions'], + }, + ui: ['show'], }, }, }); From 330c966f4f7837d337a46268469ed3528d0062dd Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 28 Jul 2020 16:30:58 +0200 Subject: [PATCH 38/75] [Uptime] Reduce miscellaneous uptime bundle size (#70632) Co-authored-by: Elastic Machine --- .../triggers_actions_ui/public/index.ts | 1 + .../plugins/uptime/common/constants/index.ts | 2 - x-pack/plugins/uptime/public/app.ts | 7 -- x-pack/plugins/uptime/public/apps/index.ts | 7 -- x-pack/plugins/uptime/public/apps/plugin.ts | 39 ++++---- .../render_app.tsx} | 44 ++++----- .../plugins/uptime/public/apps/template.html | 1 - .../uptime/public/{ => apps}/uptime_app.tsx | 23 +++-- .../public/apps/uptime_overview_fetcher.ts | 15 ++- .../components/monitor/ml/manage_ml_job.tsx | 2 +- .../monitor/ml/ml_flyout_container.tsx | 3 +- .../embeddables/__tests__/map_config.test.ts | 2 +- .../location_map/embeddables/map_config.ts | 2 +- .../alerts/alerts_containers/alert_tls.tsx | 3 - .../alerts/anomaly_alert/anomaly_alert.tsx | 3 +- .../alerts/toggle_alert_flyout_button.tsx | 2 +- .../overview/kuery_bar/kuery_bar.tsx | 7 +- .../actions_popover/actions_popover.tsx | 4 +- .../actions_popover/integration_group.tsx | 9 +- .../monitor_status_list.tsx | 5 +- .../monitor_list_status_column.tsx | 5 +- .../contexts/uptime_settings_context.tsx | 2 +- .../public/contexts/uptime_theme_context.tsx | 2 +- x-pack/plugins/uptime/public/index.ts | 2 +- .../__tests__/monitor_status.test.ts | 21 ++--- .../lib/alert_types/duration_anomaly.tsx | 18 +--- .../lazy_wrapper/duration_anomaly.tsx | 32 +++++++ .../lazy_wrapper/monitor_status.tsx | 32 +++++++ .../alert_types/lazy_wrapper/tls_alert.tsx | 32 +++++++ .../lazy_wrapper/validate_monitor_status.ts | 56 +++++++++++ .../public/lib/alert_types/monitor_status.tsx | 94 +++++-------------- .../lib/alert_types/monitor_status_title.tsx | 17 ---- .../uptime/public/lib/alert_types/tls.tsx | 17 +--- .../observability_integration/build_href.ts | 9 +- .../observability_integration/get_apm_href.ts | 3 +- .../get_infra_href.ts | 12 ++- .../get_logging_href.ts | 12 ++- x-pack/plugins/uptime/public/lib/lib.ts | 13 --- .../uptime/public/pages/certificates.tsx | 3 +- .../plugins/uptime/public/pages/settings.tsx | 12 ++- .../plugins/uptime/public/state/api/utils.ts | 6 +- .../public/state/effects/index_status.ts | 2 +- x-pack/plugins/uptime/server/kibana.index.ts | 2 +- .../server/lib/alerts/duration_anomaly.ts | 2 +- .../uptime/server/lib/alerts/status_check.ts | 2 +- .../plugins/uptime/server/lib/alerts/tls.ts | 3 +- 46 files changed, 327 insertions(+), 265 deletions(-) delete mode 100644 x-pack/plugins/uptime/public/app.ts delete mode 100644 x-pack/plugins/uptime/public/apps/index.ts rename x-pack/plugins/uptime/public/{lib/adapters/framework/new_platform_adapter.tsx => apps/render_app.tsx} (72%) delete mode 100644 x-pack/plugins/uptime/public/apps/template.html rename x-pack/plugins/uptime/public/{ => apps}/uptime_app.tsx (84%) create mode 100644 x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/duration_anomaly.tsx create mode 100644 x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/monitor_status.tsx create mode 100644 x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/tls_alert.tsx create mode 100644 x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/validate_monitor_status.ts delete mode 100644 x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index 1048e15eb1184..7808e2a7f608d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -19,6 +19,7 @@ export { ActionType, ActionTypeRegistryContract, AlertTypeParamsExpressionProps, + ValidationResult, ActionVariable, } from './types'; export { diff --git a/x-pack/plugins/uptime/common/constants/index.ts b/x-pack/plugins/uptime/common/constants/index.ts index 0ddb995301266..29ae9e47dfb8a 100644 --- a/x-pack/plugins/uptime/common/constants/index.ts +++ b/x-pack/plugins/uptime/common/constants/index.ts @@ -4,13 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './alerts'; export { CHART_FORMAT_LIMITS } from './chart_format_limits'; export { CLIENT_DEFAULTS } from './client_defaults'; export { CONTEXT_DEFAULTS } from './context_defaults'; export * from './capabilities'; export * from './settings_defaults'; -export { PLUGIN } from './plugin'; export { QUERY } from './query'; export * from './ui'; export * from './rest_api'; diff --git a/x-pack/plugins/uptime/public/app.ts b/x-pack/plugins/uptime/public/app.ts deleted file mode 100644 index b068f8a9becda..0000000000000 --- a/x-pack/plugins/uptime/public/app.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import './apps/index'; diff --git a/x-pack/plugins/uptime/public/apps/index.ts b/x-pack/plugins/uptime/public/apps/index.ts deleted file mode 100644 index 65b80d08d4f20..0000000000000 --- a/x-pack/plugins/uptime/public/apps/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { UptimePlugin } from './plugin'; diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index 9af4dea9dbb44..cf750434ab324 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -12,10 +12,11 @@ import { AppMountParameters, } from 'kibana/public'; import { DEFAULT_APP_CATEGORIES } from '../../../../../src/core/public'; -import { UMFrontendLibs } from '../lib/lib'; -import { PLUGIN } from '../../common/constants'; -import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; -import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; + +import { + FeatureCatalogueCategory, + HomePublicPluginSetup, +} from '../../../../../src/plugins/home/public'; import { EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; import { TriggersAndActionsUIPublicPluginSetup, @@ -26,10 +27,8 @@ import { DataPublicPluginStart, } from '../../../../../src/plugins/data/public'; import { alertTypeInitializers } from '../lib/alert_types'; -import { kibanaService } from '../state/kibana_service'; -import { fetchIndexStatus } from '../state/api'; -import { ObservabilityPluginSetup } from '../../../observability/public'; -import { fetchUptimeOverviewData } from './uptime_overview_fetcher'; +import { FetchDataParams, ObservabilityPluginSetup } from '../../../observability/public'; +import { PLUGIN } from '../../common/constants/plugin'; export interface ClientPluginsSetup { data: DataPublicPluginSetup; @@ -66,14 +65,23 @@ export class UptimePlugin category: FeatureCatalogueCategory.DATA, }); } + const getUptimeDataHelper = async () => { + const [coreStart] = await core.getStartServices(); + const { UptimeDataHelper } = await import('./uptime_overview_fetcher'); + return UptimeDataHelper(coreStart); + }; plugins.observability.dashboard.register({ appName: 'uptime', hasData: async () => { - const status = await fetchIndexStatus(); + const dataHelper = await getUptimeDataHelper(); + const status = await dataHelper.indexStatus(); return status.docCount > 0; }, - fetchData: fetchUptimeOverviewData, + fetchData: async (params: FetchDataParams) => { + const dataHelper = await getUptimeDataHelper(); + return await dataHelper.overviewData(params); + }, }); core.application.register({ @@ -85,22 +93,15 @@ export class UptimePlugin category: DEFAULT_APP_CATEGORIES.observability, mount: async (params: AppMountParameters) => { const [coreStart, corePlugins] = await core.getStartServices(); - const { getKibanaFrameworkAdapter } = await import( - '../lib/adapters/framework/new_platform_adapter' - ); - const { element } = params; + const { renderApp } = await import('./render_app'); - const libs: UMFrontendLibs = { - framework: getKibanaFrameworkAdapter(coreStart, plugins, corePlugins), - }; - return libs.framework.render(element); + return renderApp(coreStart, plugins, corePlugins, params); }, }); } public start(start: CoreStart, plugins: ClientPluginsStart): void { - kibanaService.core = start; alertTypeInitializers.forEach((init) => { const alertInitializer = init({ core: start, diff --git a/x-pack/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx b/x-pack/plugins/uptime/public/apps/render_app.tsx similarity index 72% rename from x-pack/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx rename to x-pack/plugins/uptime/public/apps/render_app.tsx index d6185f2c2589a..f834f8b5cdd3c 100644 --- a/x-pack/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx +++ b/x-pack/plugins/uptime/public/apps/render_app.tsx @@ -4,26 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreStart } from 'src/core/public'; import React from 'react'; import ReactDOM from 'react-dom'; import { i18n as i18nFormatter } from '@kbn/i18n'; -import { UptimeApp, UptimeAppProps } from '../../../uptime_app'; -import { getIntegratedAppAvailability } from './capabilities_adapter'; +import { AppMountParameters, CoreStart } from 'kibana/public'; +import { getIntegratedAppAvailability } from '../lib/adapters/framework/capabilities_adapter'; import { - INTEGRATED_SOLUTIONS, - PLUGIN, DEFAULT_DARK_MODE, DEFAULT_TIMEPICKER_QUICK_RANGES, -} from '../../../../common/constants'; -import { UMFrameworkAdapter } from '../../lib'; -import { ClientPluginsStart, ClientPluginsSetup } from '../../../apps/plugin'; + INTEGRATED_SOLUTIONS, +} from '../../common/constants'; +import { UptimeApp, UptimeAppProps } from './uptime_app'; +import { ClientPluginsSetup, ClientPluginsStart } from './plugin'; +import { PLUGIN } from '../../common/constants/plugin'; -export const getKibanaFrameworkAdapter = ( +export function renderApp( core: CoreStart, plugins: ClientPluginsSetup, - startPlugins: ClientPluginsStart -): UMFrameworkAdapter => { + startPlugins: ClientPluginsStart, + { element }: AppMountParameters +) { const { application: { capabilities }, chrome: { setBadge, setHelpExtension }, @@ -40,17 +40,17 @@ export const getKibanaFrameworkAdapter = ( const canSave = (capabilities.uptime.save ?? false) as boolean; const props: UptimeAppProps = { - basePath: basePath.get(), + plugins, canSave, core, + i18n, + startPlugins, + basePath: basePath.get(), darkMode: core.uiSettings.get(DEFAULT_DARK_MODE), commonlyUsedRanges: core.uiSettings.get(DEFAULT_TIMEPICKER_QUICK_RANGES), - i18n, isApmAvailable: apm, isInfraAvailable: infrastructure, isLogsAvailable: logs, - plugins, - startPlugins, renderGlobalHelpControls: () => setHelpExtension({ appName: i18nFormatter.translate('xpack.uptime.header.appName', { @@ -72,15 +72,9 @@ export const getKibanaFrameworkAdapter = ( setBreadcrumbs: core.chrome.setBreadcrumbs, }; - return { - render: async (element: any) => { - if (element) { - ReactDOM.render(, element); - } + ReactDOM.render(, element); - return () => { - ReactDOM.unmountComponentAtNode(element); - }; - }, + return () => { + ReactDOM.unmountComponentAtNode(element); }; -}; +} diff --git a/x-pack/plugins/uptime/public/apps/template.html b/x-pack/plugins/uptime/public/apps/template.html deleted file mode 100644 index a6fb47048a9b1..0000000000000 --- a/x-pack/plugins/uptime/public/apps/template.html +++ /dev/null @@ -1 +0,0 @@ -
      \ No newline at end of file diff --git a/x-pack/plugins/uptime/public/uptime_app.tsx b/x-pack/plugins/uptime/public/apps/uptime_app.tsx similarity index 84% rename from x-pack/plugins/uptime/public/uptime_app.tsx rename to x-pack/plugins/uptime/public/apps/uptime_app.tsx index 4208d79e761ed..41370f9fff492 100644 --- a/x-pack/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/plugins/uptime/public/apps/uptime_app.tsx @@ -9,24 +9,25 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import { Provider as ReduxProvider } from 'react-redux'; import { BrowserRouter as Router } from 'react-router-dom'; -import { I18nStart, ChromeBreadcrumb, CoreStart } from 'src/core/public'; -import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; -import { ClientPluginsSetup, ClientPluginsStart } from './apps/plugin'; -import { UMUpdateBadge } from './lib/lib'; +import { I18nStart, ChromeBreadcrumb, CoreStart } from 'kibana/public'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { ClientPluginsSetup, ClientPluginsStart } from './plugin'; +import { UMUpdateBadge } from '../lib/lib'; import { UptimeRefreshContextProvider, UptimeSettingsContextProvider, UptimeThemeContextProvider, UptimeStartupPluginsContextProvider, -} from './contexts'; -import { CommonlyUsedRange } from './components/common/uptime_date_picker'; -import { setBasePath } from './state/actions'; -import { PageRouter } from './routes'; +} from '../contexts'; +import { CommonlyUsedRange } from '../components/common/uptime_date_picker'; +import { setBasePath } from '../state/actions'; +import { PageRouter } from '../routes'; import { UptimeAlertsContextProvider, UptimeAlertsFlyoutWrapper, -} from './components/overview/alerts'; -import { store } from './state'; +} from '../components/overview/alerts'; +import { store } from '../state'; +import { kibanaService } from '../state/kibana_service'; export interface UptimeAppColors { danger: string; @@ -86,6 +87,8 @@ const Application = (props: UptimeAppProps) => { ); }, [canSave, renderGlobalHelpControls, setBadge]); + kibanaService.core = core; + store.dispatch(setBasePath(basePath)); return ( diff --git a/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts b/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts index d1e394dd4da6b..7e5c18f13b29e 100644 --- a/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts +++ b/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fetchPingHistogram, fetchSnapshotCount } from '../state/api'; +import { CoreStart } from 'kibana/public'; import { UptimeFetchDataResponse, FetchDataParams } from '../../../observability/public'; +import { fetchIndexStatus, fetchPingHistogram, fetchSnapshotCount } from '../state/api'; +import { kibanaService } from '../state/kibana_service'; -export async function fetchUptimeOverviewData({ +async function fetchUptimeOverviewData({ absoluteTime, relativeTime, bucketSize, @@ -52,3 +54,12 @@ export async function fetchUptimeOverviewData({ }; return response; } + +export function UptimeDataHelper(coreStart: CoreStart | null) { + kibanaService.core = coreStart!; + + return { + indexStatus: fetchIndexStatus, + overviewData: fetchUptimeOverviewData, + }; +} diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/manage_ml_job.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/manage_ml_job.tsx index 87496e91c906c..7a2899558891d 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/manage_ml_job.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ml/manage_ml_job.tsx @@ -8,7 +8,7 @@ import React, { useContext, useState } from 'react'; import { EuiButton, EuiContextMenu, EuiIcon, EuiPopover } from '@elastic/eui'; import { useSelector, useDispatch } from 'react-redux'; -import { CLIENT_ALERT_TYPES } from '../../../../common/constants'; +import { CLIENT_ALERT_TYPES } from '../../../../common/constants/alerts'; import { canDeleteMLJobSelector, hasMLJobSelector, diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout_container.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout_container.tsx index 84634f328621f..e4fe1901729d3 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout_container.tsx @@ -22,13 +22,14 @@ import { import { MLJobLink } from './ml_job_link'; import * as labels from './translations'; import { MLFlyoutView } from './ml_flyout'; -import { CLIENT_ALERT_TYPES, ML_JOB_ID } from '../../../../common/constants'; +import { ML_JOB_ID } from '../../../../common/constants'; import { UptimeRefreshContext, UptimeSettingsContext } from '../../../contexts'; import { useGetUrlParams } from '../../../hooks'; import { getDynamicSettings } from '../../../state/actions/dynamic_settings'; import { useMonitorId } from '../../../hooks'; import { kibanaService } from '../../../state/kibana_service'; import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; +import { CLIENT_ALERT_TYPES } from '../../../../common/constants/alerts'; interface Props { onClose: () => void; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/__tests__/map_config.test.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/__tests__/map_config.test.ts index 09a41bd9eb4b9..18b43434da24b 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/__tests__/map_config.test.ts +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/__tests__/map_config.test.ts @@ -7,7 +7,7 @@ import { getLayerList } from '../map_config'; import { mockLayerList } from './__mocks__/mock'; import { LocationPoint } from '../embedded_map'; -import { UptimeAppColors } from '../../../../../../uptime_app'; +import { UptimeAppColors } from '../../../../../../apps/uptime_app'; jest.mock('uuid', () => { return { diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_config.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_config.ts index e766641102a24..6f9b7e4d39c16 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_config.ts +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_config.ts @@ -6,7 +6,7 @@ import lowPolyLayerFeatures from './low_poly_layer.json'; import { LocationPoint } from './embedded_map'; -import { UptimeAppColors } from '../../../../../uptime_app'; +import { UptimeAppColors } from '../../../../../apps/uptime_app'; /** * Returns `Source/Destination Point-to-point` Map LayerList configuration, with a source, diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx index c7657c34220fc..70adcdb563bce 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx @@ -24,6 +24,3 @@ export const AlertTls: React.FC<{}> = () => { /> ); }; - -// eslint-disable-next-line import/no-default-export -export { AlertTls as default }; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/anomaly_alert/anomaly_alert.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/anomaly_alert/anomaly_alert.tsx index 4b84012575ae9..1428a7f526fc2 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/anomaly_alert/anomaly_alert.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/anomaly_alert/anomaly_alert.tsx @@ -25,8 +25,7 @@ interface Props { setAlertParams: (key: string, value: any) => void; } -// eslint-disable-next-line import/no-default-export -export default function AnomalyAlertComponent({ setAlertParams, alertParams }: Props) { +export function AnomalyAlertComponent({ setAlertParams, alertParams }: Props) { const [severity, setSeverity] = useState(DEFAULT_SEVERITY); const monitorIdStore = useSelector(monitorIdSelector); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx index 18514bd92d7a0..067972a452f27 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx @@ -15,7 +15,7 @@ import { import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { CLIENT_ALERT_TYPES } from '../../../../common/constants'; +import { CLIENT_ALERT_TYPES } from '../../../../common/constants/alerts'; import { ToggleFlyoutTranslations } from './translations'; import { ToggleAlertFlyoutButtonProps } from './alerts_containers'; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx index 9c6a4e5d418a7..9e373949aea12 100644 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx @@ -5,8 +5,7 @@ */ import React, { useState, useEffect } from 'react'; -import { uniqueId, startsWith } from 'lodash'; -import { EuiCallOut } from '@elastic/eui'; +import { EuiCallOut, htmlIdGenerator } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import { Typeahead } from './typeahead'; @@ -94,7 +93,7 @@ export function KueryBar({ setState({ ...state, suggestions: [] }); setSuggestionLimit(15); - const currentRequest = uniqueId(); + const currentRequest = htmlIdGenerator()(); currentRequestCheck = currentRequest; try { @@ -116,7 +115,7 @@ export function KueryBar({ }, ], })) || [] - ).filter((suggestion: QuerySuggestion) => !startsWith(suggestion.text, 'span.')); + ).filter((suggestion: QuerySuggestion) => !suggestion.text.startsWith('span.')); if (currentRequest !== currentRequestCheck) { return; } diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/actions_popover.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/actions_popover.tsx index 2070a374e75d0..9e96f0ca76535 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/actions_popover.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/actions_popover.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { EuiPopover, EuiButton } from '@elastic/eui'; @@ -25,7 +24,8 @@ export const ActionsPopoverComponent = ({ }: ActionsPopoverProps) => { const popoverId = `${summary.monitor_id}_popover`; - const monitorUrl: string | undefined = get(summary, 'state.url.full', undefined); + const monitorUrl: string | undefined = summary?.state?.url?.full; + const isPopoverOpen: boolean = !!popoverState && popoverState.open && popoverState.id === popoverId; return ( diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/integration_group.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/integration_group.tsx index 38aa9287b0c47..ff3b5d67375fe 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/integration_group.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/integration_group.tsx @@ -27,9 +27,12 @@ interface IntegrationGroupProps { export const extractSummaryValues = (summary: Pick) => { const domain = summary.state.url?.domain ?? ''; - const podUid = summary.state.summaryPings?.[0]?.kubernetes?.pod?.uid ?? undefined; - const containerId = summary.state.summaryPings?.[0]?.container?.id ?? undefined; - const ip = summary.state.summaryPings?.[0]?.monitor.ip ?? undefined; + + const firstCheck = summary.state.summaryPings?.[0]; + + const podUid = firstCheck?.kubernetes?.pod?.uid ?? undefined; + const containerId = firstCheck?.container?.id ?? undefined; + const ip = firstCheck?.monitor.ip ?? undefined; return { domain, diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/monitor_status_list.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/monitor_status_list.tsx index 334de6e376074..96536a357a450 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/monitor_status_list.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/monitor_status_list.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import { upperFirst } from 'lodash'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { LocationLink } from '../../../common/location_link'; @@ -29,9 +28,9 @@ export const MonitorStatusList = ({ summaryPings }: MonitorStatusListProps) => { const location = ping.observer?.geo?.name ?? UNNAMED_LOCATION; if (ping.monitor.status === STATUS.UP) { - upChecks.add(upperFirst(location)); + upChecks.add(location); } else if (ping.monitor.status === STATUS.DOWN) { - downChecks.add(upperFirst(location)); + downChecks.add(location); } }); diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_status_column.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_status_column.tsx index 68ddf512e4d3c..7140211d18807 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_status_column.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_status_column.tsx @@ -7,7 +7,6 @@ import React from 'react'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { upperFirst } from 'lodash'; import styled from 'styled-components'; import { EuiHealth, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; import { parseTimestamp } from './parse_timestamp'; @@ -83,9 +82,9 @@ export const getLocationStatus = (summaryPings: Ping[], status: string) => { const location = summaryPing?.observer?.geo?.name ?? UNNAMED_LOCATION; if (summaryPing.monitor.status === STATUS.UP) { - upPings.add(upperFirst(location)); + upPings.add(location); } else if (summaryPing.monitor.status === STATUS.DOWN) { - downPings.add(upperFirst(location)); + downPings.add(location); } }); diff --git a/x-pack/plugins/uptime/public/contexts/uptime_settings_context.tsx b/x-pack/plugins/uptime/public/contexts/uptime_settings_context.tsx index 142c6e17c5fd9..4c08e76a11aae 100644 --- a/x-pack/plugins/uptime/public/contexts/uptime_settings_context.tsx +++ b/x-pack/plugins/uptime/public/contexts/uptime_settings_context.tsx @@ -5,7 +5,7 @@ */ import React, { createContext, useMemo } from 'react'; -import { UptimeAppProps } from '../uptime_app'; +import { UptimeAppProps } from '../apps/uptime_app'; import { CLIENT_DEFAULTS, CONTEXT_DEFAULTS } from '../../common/constants'; import { CommonlyUsedRange } from '../components/common/uptime_date_picker'; import { useGetUrlParams } from '../hooks'; diff --git a/x-pack/plugins/uptime/public/contexts/uptime_theme_context.tsx b/x-pack/plugins/uptime/public/contexts/uptime_theme_context.tsx index ca2fb50cdbc67..51e8bcaed986f 100644 --- a/x-pack/plugins/uptime/public/contexts/uptime_theme_context.tsx +++ b/x-pack/plugins/uptime/public/contexts/uptime_theme_context.tsx @@ -9,7 +9,7 @@ import React, { createContext, useMemo } from 'react'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; import { DARK_THEME, LIGHT_THEME, PartialTheme, Theme } from '@elastic/charts'; -import { UptimeAppColors } from '../uptime_app'; +import { UptimeAppColors } from '../apps/uptime_app'; export interface UptimeThemeContextValues { colors: UptimeAppColors; diff --git a/x-pack/plugins/uptime/public/index.ts b/x-pack/plugins/uptime/public/index.ts index 48cf2c90ad07b..cd6efa9016830 100644 --- a/x-pack/plugins/uptime/public/index.ts +++ b/x-pack/plugins/uptime/public/index.ts @@ -5,7 +5,7 @@ */ import { PluginInitializerContext } from 'kibana/public'; -import { UptimePlugin } from './apps'; +import { UptimePlugin } from './apps/plugin'; export const plugin = (initializerContext: PluginInitializerContext) => new UptimePlugin(initializerContext); diff --git a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts index cfcb414f4815d..e999768d4e55d 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { validate, initMonitorStatusAlertType } from '../monitor_status'; +import { initMonitorStatusAlertType } from '../monitor_status'; +import { validateMonitorStatusParams as validate } from '../lazy_wrapper/validate_monitor_status'; describe('monitor status alert type', () => { describe('validate', () => { @@ -206,19 +207,11 @@ describe('monitor status alert type', () => { ", "iconClass": "uptimeApp", "id": "xpack.uptime.alerts.monitorStatus", - "name": - - , + "name": , "requiresAppContext": false, "validate": [Function], } diff --git a/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx b/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx index f0eb305461582..c1f802c2d0c91 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx @@ -5,30 +5,22 @@ */ import React from 'react'; -import { Provider as ReduxProvider } from 'react-redux'; import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; -import { CLIENT_ALERT_TYPES } from '../../../common/constants'; +import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts'; import { DurationAnomalyTranslations } from './translations'; import { AlertTypeInitializer } from '.'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import { store } from '../../state'; const { name, defaultActionMessage } = DurationAnomalyTranslations; -const AnomalyAlertExpression = React.lazy(() => - import('../../components/overview/alerts/anomaly_alert/anomaly_alert') -); +const DurationAnomalyAlert = React.lazy(() => import('./lazy_wrapper/duration_anomaly')); + export const initDurationAnomalyAlertType: AlertTypeInitializer = ({ core, plugins, }): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.DURATION_ANOMALY, iconClass: 'uptimeApp', - alertParamsExpression: (params: any) => ( - - - - - + alertParamsExpression: (params: unknown) => ( + ), name, validate: () => ({ errors: {} }), diff --git a/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/duration_anomaly.tsx b/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/duration_anomaly.tsx new file mode 100644 index 0000000000000..60f2d2e803b7b --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/duration_anomaly.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Provider as ReduxProvider } from 'react-redux'; +import { CoreStart } from 'kibana/public'; +import { store } from '../../../state'; +import { AnomalyAlertComponent } from '../../../components/overview/alerts/anomaly_alert/anomaly_alert'; +import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; +import { ClientPluginsStart } from '../../../apps/plugin'; +import { kibanaService } from '../../../state/kibana_service'; + +interface Props { + core: CoreStart; + plugins: ClientPluginsStart; + params: any; +} + +// eslint-disable-next-line import/no-default-export +export default function DurationAnomalyAlert({ core, plugins, params }: Props) { + kibanaService.core = core; + return ( + + + + + + ); +} diff --git a/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/monitor_status.tsx new file mode 100644 index 0000000000000..f6b10d0fbf968 --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/monitor_status.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Provider as ReduxProvider } from 'react-redux'; +import { CoreStart } from 'kibana/public'; +import { store } from '../../../state'; +import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; +import { ClientPluginsStart } from '../../../apps/plugin'; +import { AlertMonitorStatus } from '../../../components/overview/alerts/alerts_containers'; +import { kibanaService } from '../../../state/kibana_service'; + +interface Props { + core: CoreStart; + plugins: ClientPluginsStart; + params: any; +} + +// eslint-disable-next-line import/no-default-export +export default function MonitorStatusAlert({ core, plugins, params }: Props) { + kibanaService.core = core; + return ( + + + + + + ); +} diff --git a/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/tls_alert.tsx b/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/tls_alert.tsx new file mode 100644 index 0000000000000..413734b63ced5 --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/tls_alert.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Provider as ReduxProvider } from 'react-redux'; +import { CoreStart } from 'kibana/public'; +import { store } from '../../../state'; +import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; +import { ClientPluginsStart } from '../../../apps/plugin'; +import { AlertTls } from '../../../components/overview/alerts/alerts_containers/alert_tls'; +import { kibanaService } from '../../../state/kibana_service'; + +interface Props { + core: CoreStart; + plugins: ClientPluginsStart; + params: any; +} + +// eslint-disable-next-line import/no-default-export +export default function TLSAlert({ core, plugins, params: _params }: Props) { + kibanaService.core = core; + return ( + + + + + + ); +} diff --git a/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/validate_monitor_status.ts b/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/validate_monitor_status.ts new file mode 100644 index 0000000000000..709669c24ed0a --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/alert_types/lazy_wrapper/validate_monitor_status.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PathReporter } from 'io-ts/lib/PathReporter'; +import { isRight } from 'fp-ts/lib/Either'; +import { + AtomicStatusCheckParamsType, + MonitorAvailabilityType, + StatusCheckParamsType, +} from '../../../../common/runtime_types/alerts'; +import { ValidationResult } from '../../../../../triggers_actions_ui/public'; + +export function validateMonitorStatusParams(alertParams: any): ValidationResult { + const errors: Record = {}; + const decoded = AtomicStatusCheckParamsType.decode(alertParams); + const oldDecoded = StatusCheckParamsType.decode(alertParams); + const availabilityDecoded = MonitorAvailabilityType.decode(alertParams); + + if (!isRight(decoded) && !isRight(oldDecoded) && !isRight(availabilityDecoded)) { + return { + errors: { + typeCheckFailure: 'Provided parameters do not conform to the expected type.', + typeCheckParsingMessage: PathReporter.report(decoded), + }, + }; + } + + if ( + !(alertParams.shouldCheckAvailability ?? false) && + !(alertParams.shouldCheckStatus ?? false) + ) { + return { + errors: { + noAlertSelected: 'Alert must check for monitor status or monitor availability.', + }, + }; + } + + if (isRight(decoded) && decoded.right.shouldCheckStatus) { + const { numTimes, timerangeCount } = decoded.right; + if (numTimes < 1) { + errors.invalidNumTimes = 'Number of alert check down times must be an integer greater than 0'; + } + if (isNaN(timerangeCount)) { + errors.timeRangeStartValueNaN = 'Specified time range value must be a number'; + } + if (timerangeCount <= 0) { + errors.invalidTimeRangeValue = 'Time range value must be greater than 0'; + } + } + + return { errors }; +} diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index cb24df2357d01..e4da3eb9ef7ae 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -4,70 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Provider as ReduxProvider } from 'react-redux'; import React from 'react'; -import { isRight } from 'fp-ts/lib/Either'; -import { PathReporter } from 'io-ts/lib/PathReporter'; -import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; import { AlertTypeInitializer } from '.'; -import { - AtomicStatusCheckParamsType, - StatusCheckParamsType, - MonitorAvailabilityType, -} from '../../../common/runtime_types'; -import { MonitorStatusTitle } from './monitor_status_title'; -import { CLIENT_ALERT_TYPES } from '../../../common/constants'; -import { MonitorStatusTranslations } from './translations'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import { store } from '../../state'; - -export const validate = (alertParams: any) => { - const errors: Record = {}; - const decoded = AtomicStatusCheckParamsType.decode(alertParams); - const oldDecoded = StatusCheckParamsType.decode(alertParams); - const availabilityDecoded = MonitorAvailabilityType.decode(alertParams); - - if (!isRight(decoded) && !isRight(oldDecoded) && !isRight(availabilityDecoded)) { - return { - errors: { - typeCheckFailure: 'Provided parameters do not conform to the expected type.', - typeCheckParsingMessage: PathReporter.report(decoded), - }, - }; - } - - if ( - !(alertParams.shouldCheckAvailability ?? false) && - !(alertParams.shouldCheckStatus ?? false) - ) { - return { - errors: { - noAlertSelected: 'Alert must check for monitor status or monitor availability.', - }, - }; - } - - if (isRight(decoded) && decoded.right.shouldCheckStatus) { - const { numTimes, timerangeCount } = decoded.right; - if (numTimes < 1) { - errors.invalidNumTimes = 'Number of alert check down times must be an integer greater than 0'; - } - if (isNaN(timerangeCount)) { - errors.timeRangeStartValueNaN = 'Specified time range value must be a number'; - } - if (timerangeCount <= 0) { - errors.invalidTimeRangeValue = 'Time range value must be greater than 0'; - } - } - return { errors }; -}; +import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts'; +import { MonitorStatusTranslations } from './translations'; const { defaultActionMessage } = MonitorStatusTranslations; -const AlertMonitorStatus = React.lazy(() => - import('../../components/overview/alerts/alerts_containers/alert_monitor_status') -); +const MonitorStatusAlert = React.lazy(() => import('./lazy_wrapper/monitor_status')); + +let validateFunc: (alertParams: any) => ValidationResult; export const initMonitorStatusAlertType: AlertTypeInitializer = ({ core, @@ -75,21 +24,26 @@ export const initMonitorStatusAlertType: AlertTypeInitializer = ({ }): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.MONITOR_STATUS, name: ( - - - + ), iconClass: 'uptimeApp', - alertParamsExpression: (params: any) => { - return ( - - - - - - ); + alertParamsExpression: (params: any) => ( + + ), + validate: (alertParams: any) => { + if (!validateFunc) { + (async function loadValidate() { + const { validateMonitorStatusParams } = await import( + './lazy_wrapper/validate_monitor_status' + ); + validateFunc = validateMonitorStatusParams; + })(); + } + return validateFunc && validateFunc(alertParams); }, - validate, defaultActionMessage, requiresAppContext: false, }); diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx deleted file mode 100644 index 1e2751a4ac388..0000000000000 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export const MonitorStatusTitle = () => { - return ( - - ); -}; diff --git a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx index c541ea4ae1331..9019fc216192c 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx @@ -5,27 +5,18 @@ */ import React from 'react'; -import { Provider as ReduxProvider } from 'react-redux'; import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; -import { CLIENT_ALERT_TYPES } from '../../../common/constants'; +import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts'; import { TlsTranslations } from './translations'; import { AlertTypeInitializer } from '.'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import { store } from '../../state'; const { name, defaultActionMessage } = TlsTranslations; -const TlsAlertExpression = React.lazy(() => - import('../../components/overview/alerts/alerts_containers/alert_tls') -); +const TLSAlert = React.lazy(() => import('./lazy_wrapper/tls_alert')); export const initTlsAlertType: AlertTypeInitializer = ({ core, plugins }): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.TLS, iconClass: 'uptimeApp', - alertParamsExpression: (_params: any) => ( - - - - - + alertParamsExpression: (params: any) => ( + ), name, validate: () => ({ errors: {} }), diff --git a/x-pack/plugins/uptime/public/lib/helper/observability_integration/build_href.ts b/x-pack/plugins/uptime/public/lib/helper/observability_integration/build_href.ts index 94383262b0acd..8c96a469da492 100644 --- a/x-pack/plugins/uptime/public/lib/helper/observability_integration/build_href.ts +++ b/x-pack/plugins/uptime/public/lib/helper/observability_integration/build_href.ts @@ -4,24 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; import { Ping } from '../../../../common/runtime_types'; /** * Builds URLs to the designated features by extracting values from the provided * monitor object on a given path. Then returns the result of a provided function * to place the value in its rightful place on the URI string. - * @param checks array of summary checks containing the data to extract - * @param path the location on the object of the desired data + * @param summaryPings array of summary checks containing the data to extract + * @param getData the location on the object of the desired data * @param getHref a function that returns the full URL */ export const buildHref = ( summaryPings: Ping[], - path: string, + getData: (ping: Ping) => string | undefined, getHref: (value: string | string[] | undefined) => string | undefined ): string | undefined => { const queryValue = summaryPings - .map((ping) => get(ping, path, undefined)) + .map((ping) => getData(ping)) .filter((value: string | undefined) => value !== undefined); if (queryValue.length === 0) { return getHref(undefined); diff --git a/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_apm_href.ts b/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_apm_href.ts index 0ff5a8acb3367..a1d69950cb61a 100644 --- a/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_apm_href.ts +++ b/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_apm_href.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; import { addBasePath } from './add_base_path'; import { MonitorSummary } from '../../../../common/runtime_types'; @@ -17,6 +16,6 @@ export const getApmHref = ( addBasePath( basePath, `/app/apm#/services?kuery=${encodeURI( - `url.domain: "${get(summary, 'state.url.domain')}"` + `url.domain: "${summary?.state?.url?.domain}"` )}&rangeFrom=${dateRangeStart}&rangeTo=${dateRangeEnd}` ); diff --git a/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_infra_href.ts b/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_infra_href.ts index 33d24a0f081b4..c225382350eac 100644 --- a/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_infra_href.ts +++ b/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_infra_href.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MonitorSummary } from '../../../../common/runtime_types'; +import { MonitorSummary, Ping } from '../../../../common/runtime_types'; import { addBasePath } from './add_base_path'; import { buildHref } from './build_href'; @@ -22,7 +22,7 @@ export const getInfraContainerHref = ( `/app/metrics/link-to/container-detail/${encodeURIComponent(ret)}` ); }; - return buildHref(summary.state.summaryPings || [], 'container.id', getHref); + return buildHref(summary.state.summaryPings || [], (ping: Ping) => ping?.container?.id, getHref); }; export const getInfraKubernetesHref = ( @@ -37,7 +37,11 @@ export const getInfraKubernetesHref = ( return addBasePath(basePath, `/app/metrics/link-to/pod-detail/${encodeURIComponent(ret)}`); }; - return buildHref(summary.state.summaryPings || [], 'kubernetes.pod.uid', getHref); + return buildHref( + summary.state.summaryPings || [], + (ping: Ping) => ping?.kubernetes?.pod?.uid, + getHref + ); }; export const getInfraIpHref = (summary: MonitorSummary, basePath: string) => { @@ -63,5 +67,5 @@ export const getInfraIpHref = (summary: MonitorSummary, basePath: string) => { `/app/metrics/inventory?waffleFilter=(expression:'${encodeURIComponent(ips)}',kind:kuery)` ); }; - return buildHref(summary.state.summaryPings || [], 'monitor.ip', getHref); + return buildHref(summary.state.summaryPings || [], (ping: Ping) => ping?.monitor?.ip, getHref); }; diff --git a/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_logging_href.ts b/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_logging_href.ts index c4fee330e9763..32709882d1d21 100644 --- a/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_logging_href.ts +++ b/x-pack/plugins/uptime/public/lib/helper/observability_integration/get_logging_href.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MonitorSummary } from '../../../../common/runtime_types'; +import { MonitorSummary, Ping } from '../../../../common/runtime_types'; import { addBasePath } from './add_base_path'; import { buildHref } from './build_href'; @@ -22,7 +22,7 @@ export const getLoggingContainerHref = ( `/app/logs?logFilter=${encodeURI(`(expression:'container.id : ${ret}',kind:kuery)`)}` ); }; - return buildHref(summary.state.summaryPings || [], 'container.id', getHref); + return buildHref(summary.state.summaryPings || [], (ping: Ping) => ping?.container?.id, getHref); }; export const getLoggingKubernetesHref = (summary: MonitorSummary, basePath: string) => { @@ -36,7 +36,11 @@ export const getLoggingKubernetesHref = (summary: MonitorSummary, basePath: stri `/app/logs?logFilter=${encodeURI(`(expression:'pod.uid : ${ret}',kind:kuery)`)}` ); }; - return buildHref(summary.state.summaryPings || [], 'kubernetes.pod.uid', getHref); + return buildHref( + summary.state.summaryPings || [], + (ping: Ping) => ping?.kubernetes?.pod?.uid, + getHref + ); }; export const getLoggingIpHref = (summary: MonitorSummary, basePath: string) => { @@ -50,5 +54,5 @@ export const getLoggingIpHref = (summary: MonitorSummary, basePath: string) => { `/app/logs?logFilter=(expression:'${encodeURIComponent(`host.ip : ${ret}`)}',kind:kuery)` ); }; - return buildHref(summary.state.summaryPings || [], 'monitor.ip', getHref); + return buildHref(summary.state.summaryPings || [], (ping: Ping) => ping?.monitor?.ip, getHref); }; diff --git a/x-pack/plugins/uptime/public/lib/lib.ts b/x-pack/plugins/uptime/public/lib/lib.ts index 187dcee7adb1a..ac95f018a80a2 100644 --- a/x-pack/plugins/uptime/public/lib/lib.ts +++ b/x-pack/plugins/uptime/public/lib/lib.ts @@ -4,19 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReactElement } from 'react'; -import { AppUnmount } from 'kibana/public'; import { UMBadge } from '../badge'; -import { UptimeAppProps } from '../uptime_app'; - -export interface UMFrontendLibs { - framework: UMFrameworkAdapter; -} export type UMUpdateBadge = (badge: UMBadge) => void; - -export type BootstrapUptimeApp = (props: UptimeAppProps) => ReactElement; - -export interface UMFrameworkAdapter { - render(element: any): Promise; -} diff --git a/x-pack/plugins/uptime/public/pages/certificates.tsx b/x-pack/plugins/uptime/public/pages/certificates.tsx index 58a56a5555323..e46d228c6d21f 100644 --- a/x-pack/plugins/uptime/public/pages/certificates.tsx +++ b/x-pack/plugins/uptime/public/pages/certificates.tsx @@ -21,13 +21,14 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { useTrackPageview } from '../../../observability/public'; import { PageHeader } from './page_header'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; -import { OVERVIEW_ROUTE, SETTINGS_ROUTE, CLIENT_ALERT_TYPES } from '../../common/constants'; +import { OVERVIEW_ROUTE, SETTINGS_ROUTE } from '../../common/constants'; import { getDynamicSettings } from '../state/actions/dynamic_settings'; import { UptimeRefreshContext } from '../contexts'; import * as labels from './translations'; import { certificatesSelector, getCertificatesAction } from '../state/certificates/certificates'; import { CertificateList, CertificateSearch, CertSort } from '../components/certificates'; import { ToggleAlertFlyoutButton } from '../components/overview/alerts/alerts_containers'; +import { CLIENT_ALERT_TYPES } from '../../common/constants/alerts'; const DEFAULT_PAGE_SIZE = 10; const LOCAL_STORAGE_KEY = 'xpack.uptime.certList.pageSize'; diff --git a/x-pack/plugins/uptime/public/pages/settings.tsx b/x-pack/plugins/uptime/public/pages/settings.tsx index 602911cd41aab..89c12d0efdac1 100644 --- a/x-pack/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.tsx @@ -17,7 +17,6 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { useDispatch, useSelector } from 'react-redux'; -import { isEqual } from 'lodash'; import { useHistory } from 'react-router-dom'; import { selectDynamicSettings } from '../state/selectors'; import { getDynamicSettings, setDynamicSettings } from '../state/actions/dynamic_settings'; @@ -80,6 +79,14 @@ const getFieldErrors = (formFields: DynamicSettings | null): SettingsPageFieldEr return null; }; +const isDirtyForm = (formFields: DynamicSettings | null, settings?: DynamicSettings) => { + return ( + settings?.certAgeThreshold !== formFields?.certAgeThreshold || + settings?.certExpirationThreshold !== formFields?.certExpirationThreshold || + settings?.heartbeatIndices !== formFields?.heartbeatIndices + ); +}; + export const SettingsPage: React.FC = () => { const dss = useSelector(selectDynamicSettings); @@ -121,7 +128,8 @@ export const SettingsPage: React.FC = () => { const resetForm = () => setFormFields(dss.settings ? { ...dss.settings } : null); - const isFormDirty = !isEqual(dss.settings, formFields); + const isFormDirty = isDirtyForm(formFields, dss.settings); + const canEdit: boolean = !!useKibana().services?.application?.capabilities.uptime.configureSettings || false; const isFormDisabled = dss.loading || !canEdit; diff --git a/x-pack/plugins/uptime/public/state/api/utils.ts b/x-pack/plugins/uptime/public/state/api/utils.ts index 4f3765275c49a..e0cec56dd52cd 100644 --- a/x-pack/plugins/uptime/public/state/api/utils.ts +++ b/x-pack/plugins/uptime/public/state/api/utils.ts @@ -8,7 +8,11 @@ import { PathReporter } from 'io-ts/lib/PathReporter'; import { isRight } from 'fp-ts/lib/Either'; import { HttpFetchQuery, HttpSetup } from 'src/core/public'; import * as t from 'io-ts'; -import { isObject } from 'lodash'; + +function isObject(value: unknown) { + const type = typeof value; + return value != null && (type === 'object' || type === 'function'); +} // TODO: Copied from https://github.com/elastic/kibana/blob/master/x-pack/plugins/security_solution/common/format_errors.ts // We should figure out a better way to share this diff --git a/x-pack/plugins/uptime/public/state/effects/index_status.ts b/x-pack/plugins/uptime/public/state/effects/index_status.ts index a4b85312849a2..3917159381eb5 100644 --- a/x-pack/plugins/uptime/public/state/effects/index_status.ts +++ b/x-pack/plugins/uptime/public/state/effects/index_status.ts @@ -6,8 +6,8 @@ import { takeLeading } from 'redux-saga/effects'; import { indexStatusAction } from '../actions'; -import { fetchIndexStatus } from '../api'; import { fetchEffectFactory } from './fetch_effect'; +import { fetchIndexStatus } from '../api/index_status'; export function* fetchIndexStatusEffect() { yield takeLeading( diff --git a/x-pack/plugins/uptime/server/kibana.index.ts b/x-pack/plugins/uptime/server/kibana.index.ts index 2bf0d84a49de1..ab8d7a068b19d 100644 --- a/x-pack/plugins/uptime/server/kibana.index.ts +++ b/x-pack/plugins/uptime/server/kibana.index.ts @@ -5,7 +5,7 @@ */ import { Request, Server } from 'hapi'; -import { PLUGIN } from '../common/constants'; +import { PLUGIN } from '../common/constants/plugin'; import { compose } from './lib/compose/kibana'; import { initUptimeServer } from './uptime_server'; import { UptimeCorePlugins, UptimeCoreSetup } from './lib/adapters/framework'; diff --git a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts index 7dd357e99b83d..a71913d0eea9a 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts @@ -8,7 +8,7 @@ import moment from 'moment'; import { schema } from '@kbn/config-schema'; import { ILegacyScopedClusterClient } from 'kibana/server'; import { updateState } from './common'; -import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants'; +import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts'; import { commonStateTranslations, durationAnomalyTranslations } from './translations'; import { AnomaliesTableRecord } from '../../../../ml/common/types/anomalies'; import { getSeverityType } from '../../../../ml/common/util/anomaly_utils'; diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 2117ac4b7ed4e..a34d7eb292eef 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -21,7 +21,7 @@ import { MonitorAvailabilityType, DynamicSettings, } from '../../../common/runtime_types'; -import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants'; +import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts'; import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; import { commonStateTranslations } from './translations'; diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 61e738b088d50..d4853ad7a9cb0 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -9,7 +9,8 @@ import { schema } from '@kbn/config-schema'; import { UptimeAlertTypeFactory } from './types'; import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; -import { ACTION_GROUP_DEFINITIONS, DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; +import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; import { Cert, CertResult } from '../../../common/runtime_types'; import { commonStateTranslations, tlsTranslations } from './translations'; import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs'; From 0dbfde4f4d945a92adab66a8895fc0a129af2bd9 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 28 Jul 2020 17:34:40 +0300 Subject: [PATCH 39/75] [Functional Tests] Increase the timeout on getting the legend value on timeseries (#73279) --- test/functional/page_objects/visual_builder_page.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 8488eb8cd2749..2771982fecdea 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -315,9 +315,9 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro public async getRhythmChartLegendValue(nth = 0) { await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - const metricValue = (await find.allByCssSelector(`.echLegendItem .echLegendItem__extra`))[ - nth - ]; + const metricValue = ( + await find.allByCssSelector(`.echLegendItem .echLegendItem__extra`, 20000) + )[nth]; await metricValue.moveMouseTo(); return await metricValue.getVisibleText(); } From f87d97b629d2497fd6d63dfe5ce5fd6d3a90537e Mon Sep 17 00:00:00 2001 From: Toby Sutor <55087308+toby-sutor@users.noreply.github.com> Date: Tue, 28 Jul 2020 16:35:00 +0200 Subject: [PATCH 40/75] 32 characters requirement for xpack.reporting.encryptionKey (#72593) Similar to https://www.elastic.co/guide/en/kibana/current/alert-action-settings-kb.html#general-alert-action-settings is a 32 character minimum length required for xpack.reporting.encryptionKey --- docs/settings/reporting-settings.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index c83cd068eff5b..b31ae76d28052 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -21,7 +21,7 @@ You can configure `xpack.reporting` settings in your `kibana.yml` to: | Set to `false` to disable the {report-features}. | `xpack.reporting.encryptionKey` - | Set to any text string. By default, {kib} will generate a random key when it + | Set to an alphanumeric, at least 32 characters long text string. By default, {kib} will generate a random key when it starts, which will cause pending reports to fail after restart. Configure this setting to preserve the same key across multiple restarts and multiple instances of {kib}. From 56609049cb51d5a6edddc9929d0cb3bd5bc4cdee Mon Sep 17 00:00:00 2001 From: Toby Sutor <55087308+toby-sutor@users.noreply.github.com> Date: Tue, 28 Jul 2020 16:35:08 +0200 Subject: [PATCH 41/75] 32 characters requirement for xpack.reporting.encryptionKey (#72594) Similar to https://github.com/elastic/kibana/pull/72593 document that the string needs to be at least 32 characters long. --- docs/user/reporting/configuring-reporting.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/reporting/configuring-reporting.asciidoc b/docs/user/reporting/configuring-reporting.asciidoc index ca2d79bb2dec0..6a0c44cf4c2a4 100644 --- a/docs/user/reporting/configuring-reporting.asciidoc +++ b/docs/user/reporting/configuring-reporting.asciidoc @@ -23,7 +23,7 @@ reporting job metadata. To set a static encryption key for reporting, set the `xpack.reporting.encryptionKey` property in the `kibana.yml` -configuration file. You can use any text string as the encryption key. +configuration file. You can use any alphanumeric, at least 32 characters long text string as the encryption key. [source,yaml] -------------------------------------------------------------------------------- From cdb1c0d9a4c86cb1342005767ca697263cfafe39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Tue, 28 Jul 2020 16:44:27 +0200 Subject: [PATCH 42/75] [Logs UI] Check for presence of data instead of presence of indices in overview page fetchers (#73101) This causes the "has data" check for the observability overview page to not only check for the presence of log indices but also of log entries. --- .../log_sources/get_log_source_status.ts | 10 +++- .../public/pages/logs/stream/page_content.tsx | 2 +- .../pages/logs/stream/page_providers.tsx | 2 +- .../public/utils/logs_overview_fetchers.ts | 2 +- .../utils/logs_overview_fetches.test.ts | 48 ++++++++++++------- .../server/graphql/source_status/resolvers.ts | 2 +- .../lib/adapters/framework/adapter_types.ts | 1 + .../elasticsearch_source_status_adapter.ts | 22 +++++++-- .../plugins/infra/server/lib/source_status.ts | 19 +++++--- .../infra/server/routes/log_sources/status.ts | 11 +++-- .../infra/server/routes/source/index.ts | 8 ++-- 11 files changed, 84 insertions(+), 43 deletions(-) diff --git a/x-pack/plugins/infra/common/http_api/log_sources/get_log_source_status.ts b/x-pack/plugins/infra/common/http_api/log_sources/get_log_source_status.ts index b522d86987283..dc4e4f1ea6217 100644 --- a/x-pack/plugins/infra/common/http_api/log_sources/get_log_source_status.ts +++ b/x-pack/plugins/infra/common/http_api/log_sources/get_log_source_status.ts @@ -40,9 +40,17 @@ const logIndexFieldRT = rt.strict({ export type LogIndexField = rt.TypeOf; +const logIndexStatusRT = rt.keyof({ + missing: null, + empty: null, + available: null, +}); + +export type LogIndexStatus = rt.TypeOf; + const logSourceStatusRT = rt.strict({ logIndexFields: rt.array(logIndexFieldRT), - logIndicesExist: rt.boolean, + logIndexStatus: logIndexStatusRT, }); export type LogSourceStatus = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_content.tsx index b2a4ce65ab2b6..fe362dcf8da8c 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_content.tsx @@ -25,7 +25,7 @@ export const StreamPageContent: React.FunctionComponent = () => { return ; } else if (hasFailedLoadingSource) { return ; - } else if (sourceStatus?.logIndicesExist) { + } else if (sourceStatus?.logIndexStatus !== 'missing') { return ; } else { return ; diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx index 82c21f663bc96..1a1cc13576556 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx @@ -107,7 +107,7 @@ export const LogsPageProviders: React.FunctionComponent = ({ children }) => { const { sourceStatus } = useLogSourceContext(); // The providers assume the source is loaded, so short-circuit them otherwise - if (!sourceStatus?.logIndicesExist) { + if (sourceStatus?.logIndexStatus === 'missing') { return <>{children}; } diff --git a/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts b/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts index 53f7e00a3354c..3716e618068a3 100644 --- a/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts +++ b/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts @@ -43,7 +43,7 @@ export function getLogsHasDataFetcher( return async () => { const [core] = await getStartServices(); const sourceStatus = await callFetchLogSourceStatusAPI(DEFAULT_SOURCE_ID, core.http.fetch); - return sourceStatus.data.logIndicesExist; + return sourceStatus.data.logIndexStatus === 'available'; }; } diff --git a/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts b/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts index 6f9e41fbd08f3..a2b4e162756e3 100644 --- a/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts +++ b/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts @@ -3,17 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + +import { CoreStart } from 'kibana/public'; import { coreMock } from 'src/core/public/mocks'; import { dataPluginMock } from 'src/plugins/data/public/mocks'; -import { CoreStart } from 'kibana/public'; -import { getLogsHasDataFetcher } from './logs_overview_fetchers'; -import { InfraClientStartDeps, InfraClientStartExports } from '../types'; import { callFetchLogSourceStatusAPI } from '../containers/logs/log_source/api/fetch_log_source_status'; +import { InfraClientStartDeps, InfraClientStartExports } from '../types'; +import { getLogsHasDataFetcher } from './logs_overview_fetchers'; -// Note -// Calls to `.mock*` functions will fail the typecheck because how jest does the mocking. -// The calls will be preluded with a `@ts-expect-error` jest.mock('../containers/logs/log_source/api/fetch_log_source_status'); +const mockedCallFetchLogSourceStatusAPI = callFetchLogSourceStatusAPI as jest.MockedFunction< + typeof callFetchLogSourceStatusAPI +>; function setup() { const core = coreMock.createStart(); @@ -33,36 +34,47 @@ function setup() { describe('Logs UI Observability Homepage Functions', () => { describe('getLogsHasDataFetcher()', () => { beforeEach(() => { - // @ts-expect-error - callFetchLogSourceStatusAPI.mockReset(); + mockedCallFetchLogSourceStatusAPI.mockReset(); }); - it('should return true when some index is present', async () => { + it('should return true when non-empty indices exist', async () => { const { mockedGetStartServices } = setup(); - // @ts-expect-error - callFetchLogSourceStatusAPI.mockResolvedValue({ - data: { logIndexFields: [], logIndicesExist: true }, + mockedCallFetchLogSourceStatusAPI.mockResolvedValue({ + data: { logIndexFields: [], logIndexStatus: 'available' }, }); const hasData = getLogsHasDataFetcher(mockedGetStartServices); const response = await hasData(); - expect(callFetchLogSourceStatusAPI).toHaveBeenCalledTimes(1); + expect(mockedCallFetchLogSourceStatusAPI).toHaveBeenCalledTimes(1); expect(response).toBe(true); }); - it('should return false when no index is present', async () => { + it('should return false when only empty indices exist', async () => { + const { mockedGetStartServices } = setup(); + + mockedCallFetchLogSourceStatusAPI.mockResolvedValue({ + data: { logIndexFields: [], logIndexStatus: 'empty' }, + }); + + const hasData = getLogsHasDataFetcher(mockedGetStartServices); + const response = await hasData(); + + expect(mockedCallFetchLogSourceStatusAPI).toHaveBeenCalledTimes(1); + expect(response).toBe(false); + }); + + it('should return false when no index exists', async () => { const { mockedGetStartServices } = setup(); - // @ts-expect-error - callFetchLogSourceStatusAPI.mockResolvedValue({ - data: { logIndexFields: [], logIndicesExist: false }, + mockedCallFetchLogSourceStatusAPI.mockResolvedValue({ + data: { logIndexFields: [], logIndexStatus: 'missing' }, }); const hasData = getLogsHasDataFetcher(mockedGetStartServices); const response = await hasData(); - expect(callFetchLogSourceStatusAPI).toHaveBeenCalledTimes(1); + expect(mockedCallFetchLogSourceStatusAPI).toHaveBeenCalledTimes(1); expect(response).toBe(false); }); }); diff --git a/x-pack/plugins/infra/server/graphql/source_status/resolvers.ts b/x-pack/plugins/infra/server/graphql/source_status/resolvers.ts index 848d66058e64c..bd92dd0f7da8b 100644 --- a/x-pack/plugins/infra/server/graphql/source_status/resolvers.ts +++ b/x-pack/plugins/infra/server/graphql/source_status/resolvers.ts @@ -73,7 +73,7 @@ export const createSourceStatusResolvers = (libs: { return await libs.sourceStatus.hasLogAlias(req, source.id); }, async logIndicesExist(source, args, { req }) { - return await libs.sourceStatus.hasLogIndices(req, source.id); + return (await libs.sourceStatus.getLogIndexStatus(req, source.id)) !== 'missing'; }, async logIndices(source, args, { req }) { return await libs.sourceStatus.getLogIndexNames(req, source.id); diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 018e5098a4291..117749ae87bbe 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -38,6 +38,7 @@ export interface CallWithRequestParams extends GenericParams { fields?: string | string[]; path?: string; query?: string | object; + track_total_hits?: boolean | number; } export type InfraResponse = Lifecycle.ReturnValue; diff --git a/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts index 9bc58604f12a5..2a61e64c94fcd 100644 --- a/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts @@ -5,7 +5,7 @@ */ import { RequestHandlerContext } from 'src/core/server'; -import { InfraSourceStatusAdapter } from '../../source_status'; +import { InfraSourceStatusAdapter, SourceIndexStatus } from '../../source_status'; import { InfraDatabaseGetIndicesResponse } from '../framework'; import { KibanaFramework } from '../framework/kibana_framework_adapter'; @@ -40,7 +40,10 @@ export class InfraElasticsearchSourceStatusAdapter implements InfraSourceStatusA }); } - public async hasIndices(requestContext: RequestHandlerContext, indexNames: string) { + public async getIndexStatus( + requestContext: RequestHandlerContext, + indexNames: string + ): Promise { return await this.framework .callWithRequest(requestContext, 'search', { ignore_unavailable: true, @@ -48,12 +51,23 @@ export class InfraElasticsearchSourceStatusAdapter implements InfraSourceStatusA index: indexNames, size: 0, terminate_after: 1, + track_total_hits: 1, }) .then( - (response) => response._shards.total > 0, + (response) => { + if (response._shards.total <= 0) { + return 'missing'; + } + + if (response.hits.total.value > 0) { + return 'available'; + } + + return 'empty'; + }, (err) => { if (err.status === 404) { - return false; + return 'missing'; } throw err; } diff --git a/x-pack/plugins/infra/server/lib/source_status.ts b/x-pack/plugins/infra/server/lib/source_status.ts index 9bb953845e5a1..c383d01933562 100644 --- a/x-pack/plugins/infra/server/lib/source_status.ts +++ b/x-pack/plugins/infra/server/lib/source_status.ts @@ -69,19 +69,19 @@ export class InfraSourceStatus { ); return hasAlias; } - public async hasLogIndices( + public async getLogIndexStatus( requestContext: RequestHandlerContext, sourceId: string - ): Promise { + ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( requestContext.core.savedObjects.client, sourceId ); - const hasIndices = await this.adapter.hasIndices( + const indexStatus = await this.adapter.getIndexStatus( requestContext, sourceConfiguration.configuration.logAlias ); - return hasIndices; + return indexStatus; } public async hasMetricIndices( requestContext: RequestHandlerContext, @@ -91,16 +91,21 @@ export class InfraSourceStatus { requestContext.core.savedObjects.client, sourceId ); - const hasIndices = await this.adapter.hasIndices( + const indexStatus = await this.adapter.getIndexStatus( requestContext, sourceConfiguration.configuration.metricAlias ); - return hasIndices; + return indexStatus !== 'missing'; } } +export type SourceIndexStatus = 'missing' | 'empty' | 'available'; + export interface InfraSourceStatusAdapter { getIndexNames(requestContext: RequestHandlerContext, aliasName: string): Promise; hasAlias(requestContext: RequestHandlerContext, aliasName: string): Promise; - hasIndices(requestContext: RequestHandlerContext, indexNames: string): Promise; + getIndexStatus( + requestContext: RequestHandlerContext, + indexNames: string + ): Promise; } diff --git a/x-pack/plugins/infra/server/routes/log_sources/status.ts b/x-pack/plugins/infra/server/routes/log_sources/status.ts index 4cd85ecfe23c1..193c3541d740b 100644 --- a/x-pack/plugins/infra/server/routes/log_sources/status.ts +++ b/x-pack/plugins/infra/server/routes/log_sources/status.ts @@ -31,16 +31,17 @@ export const initLogSourceStatusRoutes = ({ const { sourceId } = request.params; try { - const logIndicesExist = await sourceStatus.hasLogIndices(requestContext, sourceId); - const logIndexFields = logIndicesExist - ? await fields.getFields(requestContext, sourceId, InfraIndexType.LOGS) - : []; + const logIndexStatus = await sourceStatus.getLogIndexStatus(requestContext, sourceId); + const logIndexFields = + logIndexStatus !== 'missing' + ? await fields.getFields(requestContext, sourceId, InfraIndexType.LOGS) + : []; return response.ok({ body: getLogSourceStatusSuccessResponsePayloadRT.encode({ data: { - logIndicesExist, logIndexFields, + logIndexStatus, }, }), }); diff --git a/x-pack/plugins/infra/server/routes/source/index.ts b/x-pack/plugins/infra/server/routes/source/index.ts index 2843897071e19..6069d3a35e54c 100644 --- a/x-pack/plugins/infra/server/routes/source/index.ts +++ b/x-pack/plugins/infra/server/routes/source/index.ts @@ -37,9 +37,9 @@ export const initSourceRoute = (libs: InfraBackendLibs) => { try { const { type, sourceId } = request.params; - const [source, logIndicesExist, metricIndicesExist, indexFields] = await Promise.all([ + const [source, logIndexStatus, metricIndicesExist, indexFields] = await Promise.all([ libs.sources.getSourceConfiguration(requestContext.core.savedObjects.client, sourceId), - libs.sourceStatus.hasLogIndices(requestContext, sourceId), + libs.sourceStatus.getLogIndexStatus(requestContext, sourceId), libs.sourceStatus.hasMetricIndices(requestContext, sourceId), libs.fields.getFields(requestContext, sourceId, typeToInfraIndexType(type)), ]); @@ -49,7 +49,7 @@ export const initSourceRoute = (libs: InfraBackendLibs) => { } const status = { - logIndicesExist, + logIndicesExist: logIndexStatus !== 'missing', metricIndicesExist, indexFields, }; @@ -83,7 +83,7 @@ export const initSourceRoute = (libs: InfraBackendLibs) => { const hasData = type === 'metrics' ? await libs.sourceStatus.hasMetricIndices(requestContext, sourceId) - : await libs.sourceStatus.hasLogIndices(requestContext, sourceId); + : (await libs.sourceStatus.getLogIndexStatus(requestContext, sourceId)) !== 'missing'; return response.ok({ body: { hasData }, From 4ede075681fae19701b3d0b004a74b1193493f7a Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 28 Jul 2020 17:51:23 +0300 Subject: [PATCH 43/75] [KP] fix doc generation for platform code (#73407) * fix doc generation for platform code * terminate process if type build failed * update types --- ...lugin-core-server.countresponse._shards.md | 11 ++ ...-plugin-core-server.countresponse.count.md | 11 ++ ...kibana-plugin-core-server.countresponse.md | 20 +++ ...-core-server.deletedocumentresponse._id.md | 11 ++ ...re-server.deletedocumentresponse._index.md | 11 ++ ...e-server.deletedocumentresponse._shards.md | 11 ++ ...ore-server.deletedocumentresponse._type.md | 11 ++ ...-server.deletedocumentresponse._version.md | 11 ++ ...ore-server.deletedocumentresponse.error.md | 13 ++ ...ore-server.deletedocumentresponse.found.md | 11 ++ ...ugin-core-server.deletedocumentresponse.md | 26 ++++ ...re-server.deletedocumentresponse.result.md | 11 ++ ...-plugin-core-server.elasticsearchclient.md | 17 +++ ...gin-core-server.explanation.description.md | 11 ++ ...-plugin-core-server.explanation.details.md | 11 ++ .../kibana-plugin-core-server.explanation.md | 21 +++ ...na-plugin-core-server.explanation.value.md | 11 ++ ...bana-plugin-core-server.getresponse._id.md | 11 ++ ...a-plugin-core-server.getresponse._index.md | 11 ++ ...n-core-server.getresponse._primary_term.md | 11 ++ ...plugin-core-server.getresponse._routing.md | 11 ++ ...-plugin-core-server.getresponse._seq_no.md | 11 ++ ...-plugin-core-server.getresponse._source.md | 11 ++ ...na-plugin-core-server.getresponse._type.md | 11 ++ ...plugin-core-server.getresponse._version.md | 11 ++ ...na-plugin-core-server.getresponse.found.md | 11 ++ .../kibana-plugin-core-server.getresponse.md | 27 ++++ .../core/server/kibana-plugin-core-server.md | 8 + ...er.savedobjectsdeletebynamespaceoptions.md | 2 +- ...objectsdeletebynamespaceoptions.refresh.md | 4 +- ...n-core-server.searchresponse._scroll_id.md | 11 ++ ...ugin-core-server.searchresponse._shards.md | 11 ++ ...core-server.searchresponse.aggregations.md | 11 ++ ...-plugin-core-server.searchresponse.hits.md | 28 ++++ ...ibana-plugin-core-server.searchresponse.md | 24 +++ ...in-core-server.searchresponse.timed_out.md | 11 ++ ...-plugin-core-server.searchresponse.took.md | 11 ++ ...na-plugin-core-server.shardsinfo.failed.md | 11 ++ .../kibana-plugin-core-server.shardsinfo.md | 22 +++ ...a-plugin-core-server.shardsinfo.skipped.md | 11 ++ ...lugin-core-server.shardsinfo.successful.md | 11 ++ ...ana-plugin-core-server.shardsinfo.total.md | 11 ++ ...lugin-core-server.shardsresponse.failed.md | 11 ++ ...ibana-plugin-core-server.shardsresponse.md | 22 +++ ...ugin-core-server.shardsresponse.skipped.md | 11 ++ ...n-core-server.shardsresponse.successful.md | 11 ++ ...plugin-core-server.shardsresponse.total.md | 11 ++ ...plugin-plugins-data-server.isearchsetup.md | 2 +- src/core/public/public.api.md | 122 +-------------- src/core/server/elasticsearch/client/types.ts | 32 +++- src/core/server/elasticsearch/index.ts | 6 +- src/core/server/index.ts | 7 + src/core/server/server.api.md | 142 +++++++++++++++++- src/dev/run_check_published_api_changes.ts | 47 +++--- src/plugins/data/public/public.api.md | 120 +-------------- src/plugins/data/server/server.api.md | 5 + 56 files changed, 811 insertions(+), 280 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.countresponse._shards.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.countresponse.count.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.countresponse.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._id.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._index.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._shards.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._type.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._version.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.error.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.found.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.result.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.elasticsearchclient.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.explanation.description.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.explanation.details.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.explanation.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.explanation.value.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getresponse._id.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getresponse._index.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getresponse._primary_term.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getresponse._routing.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getresponse._seq_no.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getresponse._source.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getresponse._type.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getresponse._version.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getresponse.found.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getresponse.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.searchresponse._scroll_id.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.searchresponse._shards.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.searchresponse.aggregations.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.searchresponse.hits.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.searchresponse.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.searchresponse.timed_out.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.searchresponse.took.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.shardsinfo.failed.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.shardsinfo.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.shardsinfo.skipped.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.shardsinfo.successful.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.shardsinfo.total.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.shardsresponse.failed.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.shardsresponse.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.shardsresponse.skipped.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.shardsresponse.successful.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.shardsresponse.total.md diff --git a/docs/development/core/server/kibana-plugin-core-server.countresponse._shards.md b/docs/development/core/server/kibana-plugin-core-server.countresponse._shards.md new file mode 100644 index 0000000000000..0f31a554e2208 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.countresponse._shards.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CountResponse](./kibana-plugin-core-server.countresponse.md) > [\_shards](./kibana-plugin-core-server.countresponse._shards.md) + +## CountResponse.\_shards property + +Signature: + +```typescript +_shards: ShardsInfo; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.countresponse.count.md b/docs/development/core/server/kibana-plugin-core-server.countresponse.count.md new file mode 100644 index 0000000000000..3cd1a6aaf6644 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.countresponse.count.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CountResponse](./kibana-plugin-core-server.countresponse.md) > [count](./kibana-plugin-core-server.countresponse.count.md) + +## CountResponse.count property + +Signature: + +```typescript +count: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.countresponse.md b/docs/development/core/server/kibana-plugin-core-server.countresponse.md new file mode 100644 index 0000000000000..f8664f4878f46 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.countresponse.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CountResponse](./kibana-plugin-core-server.countresponse.md) + +## CountResponse interface + + +Signature: + +```typescript +export interface CountResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [\_shards](./kibana-plugin-core-server.countresponse._shards.md) | ShardsInfo | | +| [count](./kibana-plugin-core-server.countresponse.count.md) | number | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._id.md b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._id.md new file mode 100644 index 0000000000000..ccc6a76361f26 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._id.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) > [\_id](./kibana-plugin-core-server.deletedocumentresponse._id.md) + +## DeleteDocumentResponse.\_id property + +Signature: + +```typescript +_id: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._index.md b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._index.md new file mode 100644 index 0000000000000..a9a04bb2b2ed7 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._index.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) > [\_index](./kibana-plugin-core-server.deletedocumentresponse._index.md) + +## DeleteDocumentResponse.\_index property + +Signature: + +```typescript +_index: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._shards.md b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._shards.md new file mode 100644 index 0000000000000..e3d5e9208db0a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._shards.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) > [\_shards](./kibana-plugin-core-server.deletedocumentresponse._shards.md) + +## DeleteDocumentResponse.\_shards property + +Signature: + +```typescript +_shards: ShardsResponse; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._type.md b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._type.md new file mode 100644 index 0000000000000..690852e20a76e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) > [\_type](./kibana-plugin-core-server.deletedocumentresponse._type.md) + +## DeleteDocumentResponse.\_type property + +Signature: + +```typescript +_type: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._version.md b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._version.md new file mode 100644 index 0000000000000..acfe8ef55ae71 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse._version.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) > [\_version](./kibana-plugin-core-server.deletedocumentresponse._version.md) + +## DeleteDocumentResponse.\_version property + +Signature: + +```typescript +_version: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.error.md b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.error.md new file mode 100644 index 0000000000000..aafe850188998 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.error.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) > [error](./kibana-plugin-core-server.deletedocumentresponse.error.md) + +## DeleteDocumentResponse.error property + +Signature: + +```typescript +error?: { + type: string; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.found.md b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.found.md new file mode 100644 index 0000000000000..00bc89bda66ed --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.found.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) > [found](./kibana-plugin-core-server.deletedocumentresponse.found.md) + +## DeleteDocumentResponse.found property + +Signature: + +```typescript +found: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.md b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.md new file mode 100644 index 0000000000000..e8ac7d2fd8ec1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) + +## DeleteDocumentResponse interface + + +Signature: + +```typescript +export interface DeleteDocumentResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [\_id](./kibana-plugin-core-server.deletedocumentresponse._id.md) | string | | +| [\_index](./kibana-plugin-core-server.deletedocumentresponse._index.md) | string | | +| [\_shards](./kibana-plugin-core-server.deletedocumentresponse._shards.md) | ShardsResponse | | +| [\_type](./kibana-plugin-core-server.deletedocumentresponse._type.md) | string | | +| [\_version](./kibana-plugin-core-server.deletedocumentresponse._version.md) | number | | +| [error](./kibana-plugin-core-server.deletedocumentresponse.error.md) | {
      type: string;
      } | | +| [found](./kibana-plugin-core-server.deletedocumentresponse.found.md) | boolean | | +| [result](./kibana-plugin-core-server.deletedocumentresponse.result.md) | string | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.result.md b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.result.md new file mode 100644 index 0000000000000..88f7568d3d9bc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deletedocumentresponse.result.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) > [result](./kibana-plugin-core-server.deletedocumentresponse.result.md) + +## DeleteDocumentResponse.result property + +Signature: + +```typescript +result: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchclient.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchclient.md new file mode 100644 index 0000000000000..279262aa6a5ec --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchclient.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchClient](./kibana-plugin-core-server.elasticsearchclient.md) + +## ElasticsearchClient type + +Client used to query the elasticsearch cluster. + +Signature: + +```typescript +export declare type ElasticsearchClient = Omit & { + transport: { + request(params: TransportRequestParams, options?: TransportRequestOptions): TransportRequestPromise; + }; +}; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.explanation.description.md b/docs/development/core/server/kibana-plugin-core-server.explanation.description.md new file mode 100644 index 0000000000000..37fc90f5ac5d8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.explanation.description.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Explanation](./kibana-plugin-core-server.explanation.md) > [description](./kibana-plugin-core-server.explanation.description.md) + +## Explanation.description property + +Signature: + +```typescript +description: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.explanation.details.md b/docs/development/core/server/kibana-plugin-core-server.explanation.details.md new file mode 100644 index 0000000000000..afba9175d86cf --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.explanation.details.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Explanation](./kibana-plugin-core-server.explanation.md) > [details](./kibana-plugin-core-server.explanation.details.md) + +## Explanation.details property + +Signature: + +```typescript +details: Explanation[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.explanation.md b/docs/development/core/server/kibana-plugin-core-server.explanation.md new file mode 100644 index 0000000000000..eb18910c4795b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.explanation.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Explanation](./kibana-plugin-core-server.explanation.md) + +## Explanation interface + + +Signature: + +```typescript +export interface Explanation +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [description](./kibana-plugin-core-server.explanation.description.md) | string | | +| [details](./kibana-plugin-core-server.explanation.details.md) | Explanation[] | | +| [value](./kibana-plugin-core-server.explanation.value.md) | number | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.explanation.value.md b/docs/development/core/server/kibana-plugin-core-server.explanation.value.md new file mode 100644 index 0000000000000..b10f60176b0c8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.explanation.value.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Explanation](./kibana-plugin-core-server.explanation.md) > [value](./kibana-plugin-core-server.explanation.value.md) + +## Explanation.value property + +Signature: + +```typescript +value: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getresponse._id.md b/docs/development/core/server/kibana-plugin-core-server.getresponse._id.md new file mode 100644 index 0000000000000..d31b61f3962c8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getresponse._id.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetResponse](./kibana-plugin-core-server.getresponse.md) > [\_id](./kibana-plugin-core-server.getresponse._id.md) + +## GetResponse.\_id property + +Signature: + +```typescript +_id: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getresponse._index.md b/docs/development/core/server/kibana-plugin-core-server.getresponse._index.md new file mode 100644 index 0000000000000..0353ec1a17b2c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getresponse._index.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetResponse](./kibana-plugin-core-server.getresponse.md) > [\_index](./kibana-plugin-core-server.getresponse._index.md) + +## GetResponse.\_index property + +Signature: + +```typescript +_index: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getresponse._primary_term.md b/docs/development/core/server/kibana-plugin-core-server.getresponse._primary_term.md new file mode 100644 index 0000000000000..8412302ab727d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getresponse._primary_term.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetResponse](./kibana-plugin-core-server.getresponse.md) > [\_primary\_term](./kibana-plugin-core-server.getresponse._primary_term.md) + +## GetResponse.\_primary\_term property + +Signature: + +```typescript +_primary_term: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getresponse._routing.md b/docs/development/core/server/kibana-plugin-core-server.getresponse._routing.md new file mode 100644 index 0000000000000..1af3ed31ee112 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getresponse._routing.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetResponse](./kibana-plugin-core-server.getresponse.md) > [\_routing](./kibana-plugin-core-server.getresponse._routing.md) + +## GetResponse.\_routing property + +Signature: + +```typescript +_routing?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getresponse._seq_no.md b/docs/development/core/server/kibana-plugin-core-server.getresponse._seq_no.md new file mode 100644 index 0000000000000..e8d72563f8149 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getresponse._seq_no.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetResponse](./kibana-plugin-core-server.getresponse.md) > [\_seq\_no](./kibana-plugin-core-server.getresponse._seq_no.md) + +## GetResponse.\_seq\_no property + +Signature: + +```typescript +_seq_no: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getresponse._source.md b/docs/development/core/server/kibana-plugin-core-server.getresponse._source.md new file mode 100644 index 0000000000000..97aacb42992a3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getresponse._source.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetResponse](./kibana-plugin-core-server.getresponse.md) > [\_source](./kibana-plugin-core-server.getresponse._source.md) + +## GetResponse.\_source property + +Signature: + +```typescript +_source: T; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getresponse._type.md b/docs/development/core/server/kibana-plugin-core-server.getresponse._type.md new file mode 100644 index 0000000000000..b3205e2fe91d7 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getresponse._type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetResponse](./kibana-plugin-core-server.getresponse.md) > [\_type](./kibana-plugin-core-server.getresponse._type.md) + +## GetResponse.\_type property + +Signature: + +```typescript +_type: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getresponse._version.md b/docs/development/core/server/kibana-plugin-core-server.getresponse._version.md new file mode 100644 index 0000000000000..23d3a8c91f4a2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getresponse._version.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetResponse](./kibana-plugin-core-server.getresponse.md) > [\_version](./kibana-plugin-core-server.getresponse._version.md) + +## GetResponse.\_version property + +Signature: + +```typescript +_version: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getresponse.found.md b/docs/development/core/server/kibana-plugin-core-server.getresponse.found.md new file mode 100644 index 0000000000000..8d34a3e743cca --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getresponse.found.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetResponse](./kibana-plugin-core-server.getresponse.md) > [found](./kibana-plugin-core-server.getresponse.found.md) + +## GetResponse.found property + +Signature: + +```typescript +found: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getresponse.md b/docs/development/core/server/kibana-plugin-core-server.getresponse.md new file mode 100644 index 0000000000000..bab3092c6b1fa --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getresponse.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetResponse](./kibana-plugin-core-server.getresponse.md) + +## GetResponse interface + + +Signature: + +```typescript +export interface GetResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [\_id](./kibana-plugin-core-server.getresponse._id.md) | string | | +| [\_index](./kibana-plugin-core-server.getresponse._index.md) | string | | +| [\_primary\_term](./kibana-plugin-core-server.getresponse._primary_term.md) | number | | +| [\_routing](./kibana-plugin-core-server.getresponse._routing.md) | string | | +| [\_seq\_no](./kibana-plugin-core-server.getresponse._seq_no.md) | number | | +| [\_source](./kibana-plugin-core-server.getresponse._source.md) | T | | +| [\_type](./kibana-plugin-core-server.getresponse._type.md) | string | | +| [\_version](./kibana-plugin-core-server.getresponse._version.md) | number | | +| [found](./kibana-plugin-core-server.getresponse.found.md) | boolean | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 61ffc532f0de5..95b7627398b45 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -74,7 +74,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [CoreSetup](./kibana-plugin-core-server.coresetup.md) | Context passed to the plugins setup method. | | [CoreStart](./kibana-plugin-core-server.corestart.md) | Context passed to the plugins start method. | | [CoreStatus](./kibana-plugin-core-server.corestatus.md) | Status of core services. | +| [CountResponse](./kibana-plugin-core-server.countresponse.md) | | | [CustomHttpResponseOptions](./kibana-plugin-core-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | +| [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) | | | [DeprecationAPIClientParams](./kibana-plugin-core-server.deprecationapiclientparams.md) | | | [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) | | | [DeprecationInfo](./kibana-plugin-core-server.deprecationinfo.md) | | @@ -85,7 +87,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) | | | [EnvironmentMode](./kibana-plugin-core-server.environmentmode.md) | | | [ErrorHttpResponseOptions](./kibana-plugin-core-server.errorhttpresponseoptions.md) | HTTP response parameters | +| [Explanation](./kibana-plugin-core-server.explanation.md) | | | [FakeRequest](./kibana-plugin-core-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | +| [GetResponse](./kibana-plugin-core-server.getresponse.md) | | | [HttpAuth](./kibana-plugin-core-server.httpauth.md) | | | [HttpResources](./kibana-plugin-core-server.httpresources.md) | HttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP. Provides API allowing plug-ins to respond with: - a pre-configured HTML page bootstrapping Kibana client app - custom HTML page - custom JS script file. | | [HttpResourcesRenderOptions](./kibana-plugin-core-server.httpresourcesrenderoptions.md) | Allows to configure HTTP response parameters | @@ -189,11 +193,14 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsTypeMappingDefinition](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) | Describe a saved object type mapping. | | [SavedObjectsUpdateOptions](./kibana-plugin-core-server.savedobjectsupdateoptions.md) | | | [SavedObjectsUpdateResponse](./kibana-plugin-core-server.savedobjectsupdateresponse.md) | | +| [SearchResponse](./kibana-plugin-core-server.searchresponse.md) | | | [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) | The current status of a service at a point in time. | | [SessionCookieValidationResult](./kibana-plugin-core-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. | | [SessionStorage](./kibana-plugin-core-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | | [SessionStorageCookieOptions](./kibana-plugin-core-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | | [SessionStorageFactory](./kibana-plugin-core-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | +| [ShardsInfo](./kibana-plugin-core-server.shardsinfo.md) | | +| [ShardsResponse](./kibana-plugin-core-server.shardsresponse.md) | | | [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) | API for accessing status of Core and this plugin's dependencies as well as for customizing this plugin's status. | | [StringValidationRegex](./kibana-plugin-core-server.stringvalidationregex.md) | StringValidation with regex object | | [StringValidationRegexString](./kibana-plugin-core-server.stringvalidationregexstring.md) | StringValidation as regex string | @@ -228,6 +235,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ConfigDeprecationProvider](./kibana-plugin-core-server.configdeprecationprovider.md) | A provider that should returns a list of [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md).See [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) for more usage examples. | | [ConfigPath](./kibana-plugin-core-server.configpath.md) | | | [DestructiveRouteMethod](./kibana-plugin-core-server.destructiveroutemethod.md) | Set of HTTP methods changing the state of the server. | +| [ElasticsearchClient](./kibana-plugin-core-server.elasticsearchclient.md) | Client used to query the elasticsearch cluster. | | [Freezable](./kibana-plugin-core-server.freezable.md) | | | [GetAuthHeaders](./kibana-plugin-core-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | | [GetAuthState](./kibana-plugin-core-server.getauthstate.md) | Gets authentication state for a request. Returned by auth interceptor. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.md index 3fac0d889c6ce..ba81a3e8c32d0 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.md @@ -15,5 +15,5 @@ export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOp | Property | Type | Description | | --- | --- | --- | -| [refresh](./kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | +| [refresh](./kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.refresh.md) | boolean | The Elasticsearch supports only boolean flag for this operation | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.refresh.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.refresh.md index c67866a5553a0..52b562e8e22b7 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.refresh.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.refresh.md @@ -4,10 +4,10 @@ ## SavedObjectsDeleteByNamespaceOptions.refresh property -The Elasticsearch Refresh setting for this operation +The Elasticsearch supports only boolean flag for this operation Signature: ```typescript -refresh?: MutatingOperationRefreshSetting; +refresh?: boolean; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.searchresponse._scroll_id.md b/docs/development/core/server/kibana-plugin-core-server.searchresponse._scroll_id.md new file mode 100644 index 0000000000000..a9dd0e76475fd --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.searchresponse._scroll_id.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SearchResponse](./kibana-plugin-core-server.searchresponse.md) > [\_scroll\_id](./kibana-plugin-core-server.searchresponse._scroll_id.md) + +## SearchResponse.\_scroll\_id property + +Signature: + +```typescript +_scroll_id?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.searchresponse._shards.md b/docs/development/core/server/kibana-plugin-core-server.searchresponse._shards.md new file mode 100644 index 0000000000000..e090ad20e8bc8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.searchresponse._shards.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SearchResponse](./kibana-plugin-core-server.searchresponse.md) > [\_shards](./kibana-plugin-core-server.searchresponse._shards.md) + +## SearchResponse.\_shards property + +Signature: + +```typescript +_shards: ShardsResponse; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.searchresponse.aggregations.md b/docs/development/core/server/kibana-plugin-core-server.searchresponse.aggregations.md new file mode 100644 index 0000000000000..686e6f2aa05e9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.searchresponse.aggregations.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SearchResponse](./kibana-plugin-core-server.searchresponse.md) > [aggregations](./kibana-plugin-core-server.searchresponse.aggregations.md) + +## SearchResponse.aggregations property + +Signature: + +```typescript +aggregations?: any; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.searchresponse.hits.md b/docs/development/core/server/kibana-plugin-core-server.searchresponse.hits.md new file mode 100644 index 0000000000000..1629e77425525 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.searchresponse.hits.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SearchResponse](./kibana-plugin-core-server.searchresponse.md) > [hits](./kibana-plugin-core-server.searchresponse.hits.md) + +## SearchResponse.hits property + +Signature: + +```typescript +hits: { + total: number; + max_score: number; + hits: Array<{ + _index: string; + _type: string; + _id: string; + _score: number; + _source: T; + _version?: number; + _explanation?: Explanation; + fields?: any; + highlight?: any; + inner_hits?: any; + matched_queries?: string[]; + sort?: string[]; + }>; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.searchresponse.md b/docs/development/core/server/kibana-plugin-core-server.searchresponse.md new file mode 100644 index 0000000000000..b53cbf0d87f24 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.searchresponse.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SearchResponse](./kibana-plugin-core-server.searchresponse.md) + +## SearchResponse interface + + +Signature: + +```typescript +export interface SearchResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [\_scroll\_id](./kibana-plugin-core-server.searchresponse._scroll_id.md) | string | | +| [\_shards](./kibana-plugin-core-server.searchresponse._shards.md) | ShardsResponse | | +| [aggregations](./kibana-plugin-core-server.searchresponse.aggregations.md) | any | | +| [hits](./kibana-plugin-core-server.searchresponse.hits.md) | {
      total: number;
      max_score: number;
      hits: Array<{
      _index: string;
      _type: string;
      _id: string;
      _score: number;
      _source: T;
      _version?: number;
      _explanation?: Explanation;
      fields?: any;
      highlight?: any;
      inner_hits?: any;
      matched_queries?: string[];
      sort?: string[];
      }>;
      } | | +| [timed\_out](./kibana-plugin-core-server.searchresponse.timed_out.md) | boolean | | +| [took](./kibana-plugin-core-server.searchresponse.took.md) | number | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.searchresponse.timed_out.md b/docs/development/core/server/kibana-plugin-core-server.searchresponse.timed_out.md new file mode 100644 index 0000000000000..a3488117cd874 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.searchresponse.timed_out.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SearchResponse](./kibana-plugin-core-server.searchresponse.md) > [timed\_out](./kibana-plugin-core-server.searchresponse.timed_out.md) + +## SearchResponse.timed\_out property + +Signature: + +```typescript +timed_out: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.searchresponse.took.md b/docs/development/core/server/kibana-plugin-core-server.searchresponse.took.md new file mode 100644 index 0000000000000..8c9c0b0f7c420 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.searchresponse.took.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SearchResponse](./kibana-plugin-core-server.searchresponse.md) > [took](./kibana-plugin-core-server.searchresponse.took.md) + +## SearchResponse.took property + +Signature: + +```typescript +took: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.shardsinfo.failed.md b/docs/development/core/server/kibana-plugin-core-server.shardsinfo.failed.md new file mode 100644 index 0000000000000..a47fc1263be41 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.shardsinfo.failed.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ShardsInfo](./kibana-plugin-core-server.shardsinfo.md) > [failed](./kibana-plugin-core-server.shardsinfo.failed.md) + +## ShardsInfo.failed property + +Signature: + +```typescript +failed: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.shardsinfo.md b/docs/development/core/server/kibana-plugin-core-server.shardsinfo.md new file mode 100644 index 0000000000000..9eafe3792c14a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.shardsinfo.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ShardsInfo](./kibana-plugin-core-server.shardsinfo.md) + +## ShardsInfo interface + + +Signature: + +```typescript +export interface ShardsInfo +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [failed](./kibana-plugin-core-server.shardsinfo.failed.md) | number | | +| [skipped](./kibana-plugin-core-server.shardsinfo.skipped.md) | number | | +| [successful](./kibana-plugin-core-server.shardsinfo.successful.md) | number | | +| [total](./kibana-plugin-core-server.shardsinfo.total.md) | number | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.shardsinfo.skipped.md b/docs/development/core/server/kibana-plugin-core-server.shardsinfo.skipped.md new file mode 100644 index 0000000000000..0c87831edd6ca --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.shardsinfo.skipped.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ShardsInfo](./kibana-plugin-core-server.shardsinfo.md) > [skipped](./kibana-plugin-core-server.shardsinfo.skipped.md) + +## ShardsInfo.skipped property + +Signature: + +```typescript +skipped: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.shardsinfo.successful.md b/docs/development/core/server/kibana-plugin-core-server.shardsinfo.successful.md new file mode 100644 index 0000000000000..c927adb39932a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.shardsinfo.successful.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ShardsInfo](./kibana-plugin-core-server.shardsinfo.md) > [successful](./kibana-plugin-core-server.shardsinfo.successful.md) + +## ShardsInfo.successful property + +Signature: + +```typescript +successful: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.shardsinfo.total.md b/docs/development/core/server/kibana-plugin-core-server.shardsinfo.total.md new file mode 100644 index 0000000000000..820c8a70fd222 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.shardsinfo.total.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ShardsInfo](./kibana-plugin-core-server.shardsinfo.md) > [total](./kibana-plugin-core-server.shardsinfo.total.md) + +## ShardsInfo.total property + +Signature: + +```typescript +total: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.shardsresponse.failed.md b/docs/development/core/server/kibana-plugin-core-server.shardsresponse.failed.md new file mode 100644 index 0000000000000..7f7a173af2e58 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.shardsresponse.failed.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ShardsResponse](./kibana-plugin-core-server.shardsresponse.md) > [failed](./kibana-plugin-core-server.shardsresponse.failed.md) + +## ShardsResponse.failed property + +Signature: + +```typescript +failed: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.shardsresponse.md b/docs/development/core/server/kibana-plugin-core-server.shardsresponse.md new file mode 100644 index 0000000000000..722ffd8efdb57 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.shardsresponse.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ShardsResponse](./kibana-plugin-core-server.shardsresponse.md) + +## ShardsResponse interface + + +Signature: + +```typescript +export interface ShardsResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [failed](./kibana-plugin-core-server.shardsresponse.failed.md) | number | | +| [skipped](./kibana-plugin-core-server.shardsresponse.skipped.md) | number | | +| [successful](./kibana-plugin-core-server.shardsresponse.successful.md) | number | | +| [total](./kibana-plugin-core-server.shardsresponse.total.md) | number | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.shardsresponse.skipped.md b/docs/development/core/server/kibana-plugin-core-server.shardsresponse.skipped.md new file mode 100644 index 0000000000000..b01c3501fe022 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.shardsresponse.skipped.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ShardsResponse](./kibana-plugin-core-server.shardsresponse.md) > [skipped](./kibana-plugin-core-server.shardsresponse.skipped.md) + +## ShardsResponse.skipped property + +Signature: + +```typescript +skipped: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.shardsresponse.successful.md b/docs/development/core/server/kibana-plugin-core-server.shardsresponse.successful.md new file mode 100644 index 0000000000000..23c6ff0519ed7 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.shardsresponse.successful.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ShardsResponse](./kibana-plugin-core-server.shardsresponse.md) > [successful](./kibana-plugin-core-server.shardsresponse.successful.md) + +## ShardsResponse.successful property + +Signature: + +```typescript +successful: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.shardsresponse.total.md b/docs/development/core/server/kibana-plugin-core-server.shardsresponse.total.md new file mode 100644 index 0000000000000..e669f6216a10f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.shardsresponse.total.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ShardsResponse](./kibana-plugin-core-server.shardsresponse.md) > [total](./kibana-plugin-core-server.shardsresponse.total.md) + +## ShardsResponse.total property + +Signature: + +```typescript +total: number; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md index 3afba80064f08..d9749bc44f45a 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md @@ -14,6 +14,6 @@ export interface ISearchSetup | Property | Type | Description | | --- | --- | --- | -| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | TRegisterSearchStrategy | Extension point exposed for other plugins to register their own search strategies. | +| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | (name: string, strategy: ISearchStrategy) => void | Extension point exposed for other plugins to register their own search strategies. | | [usage](./kibana-plugin-plugins-data-server.isearchsetup.usage.md) | SearchUsage | Used internally for telemetry | diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index c811209dfa80f..9b421e0172df0 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -5,147 +5,35 @@ ```ts import { Action } from 'history'; +import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import Boom from 'boom'; -import { BulkIndexDocumentsParams } from 'elasticsearch'; -import { CatAliasesParams } from 'elasticsearch'; -import { CatAllocationParams } from 'elasticsearch'; -import { CatCommonParams } from 'elasticsearch'; -import { CatFielddataParams } from 'elasticsearch'; -import { CatHealthParams } from 'elasticsearch'; -import { CatHelpParams } from 'elasticsearch'; -import { CatIndicesParams } from 'elasticsearch'; -import { CatRecoveryParams } from 'elasticsearch'; -import { CatSegmentsParams } from 'elasticsearch'; -import { CatShardsParams } from 'elasticsearch'; -import { CatSnapshotsParams } from 'elasticsearch'; -import { CatTasksParams } from 'elasticsearch'; -import { CatThreadPoolParams } from 'elasticsearch'; -import { ClearScrollParams } from 'elasticsearch'; -import { Client } from 'elasticsearch'; -import { ClusterAllocationExplainParams } from 'elasticsearch'; -import { ClusterGetSettingsParams } from 'elasticsearch'; -import { ClusterHealthParams } from 'elasticsearch'; -import { ClusterPendingTasksParams } from 'elasticsearch'; -import { ClusterPutSettingsParams } from 'elasticsearch'; -import { ClusterRerouteParams } from 'elasticsearch'; -import { ClusterStateParams } from 'elasticsearch'; -import { ClusterStatsParams } from 'elasticsearch'; -import { CountParams } from 'elasticsearch'; -import { CreateDocumentParams } from 'elasticsearch'; -import { DeleteDocumentByQueryParams } from 'elasticsearch'; -import { DeleteDocumentParams } from 'elasticsearch'; -import { DeleteScriptParams } from 'elasticsearch'; -import { DeleteTemplateParams } from 'elasticsearch'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; import { EuiGlobalToastListToast } from '@elastic/eui'; import { ExclusiveUnion } from '@elastic/eui'; -import { ExistsParams } from 'elasticsearch'; -import { ExplainParams } from 'elasticsearch'; -import { FieldStatsParams } from 'elasticsearch'; -import { GenericParams } from 'elasticsearch'; -import { GetParams } from 'elasticsearch'; -import { GetResponse } from 'elasticsearch'; -import { GetScriptParams } from 'elasticsearch'; -import { GetSourceParams } from 'elasticsearch'; -import { GetTemplateParams } from 'elasticsearch'; import { History } from 'history'; import { Href } from 'history'; import { IconType } from '@elastic/eui'; -import { IndexDocumentParams } from 'elasticsearch'; -import { IndicesAnalyzeParams } from 'elasticsearch'; -import { IndicesClearCacheParams } from 'elasticsearch'; -import { IndicesCloseParams } from 'elasticsearch'; -import { IndicesCreateParams } from 'elasticsearch'; -import { IndicesDeleteAliasParams } from 'elasticsearch'; -import { IndicesDeleteParams } from 'elasticsearch'; -import { IndicesDeleteTemplateParams } from 'elasticsearch'; -import { IndicesExistsAliasParams } from 'elasticsearch'; -import { IndicesExistsParams } from 'elasticsearch'; -import { IndicesExistsTemplateParams } from 'elasticsearch'; -import { IndicesExistsTypeParams } from 'elasticsearch'; -import { IndicesFlushParams } from 'elasticsearch'; -import { IndicesFlushSyncedParams } from 'elasticsearch'; -import { IndicesForcemergeParams } from 'elasticsearch'; -import { IndicesGetAliasParams } from 'elasticsearch'; -import { IndicesGetFieldMappingParams } from 'elasticsearch'; -import { IndicesGetMappingParams } from 'elasticsearch'; -import { IndicesGetParams } from 'elasticsearch'; -import { IndicesGetSettingsParams } from 'elasticsearch'; -import { IndicesGetTemplateParams } from 'elasticsearch'; -import { IndicesGetUpgradeParams } from 'elasticsearch'; -import { IndicesOpenParams } from 'elasticsearch'; -import { IndicesPutAliasParams } from 'elasticsearch'; -import { IndicesPutMappingParams } from 'elasticsearch'; -import { IndicesPutSettingsParams } from 'elasticsearch'; -import { IndicesPutTemplateParams } from 'elasticsearch'; -import { IndicesRecoveryParams } from 'elasticsearch'; -import { IndicesRefreshParams } from 'elasticsearch'; -import { IndicesRolloverParams } from 'elasticsearch'; -import { IndicesSegmentsParams } from 'elasticsearch'; -import { IndicesShardStoresParams } from 'elasticsearch'; -import { IndicesShrinkParams } from 'elasticsearch'; -import { IndicesStatsParams } from 'elasticsearch'; -import { IndicesUpdateAliasesParams } from 'elasticsearch'; -import { IndicesUpgradeParams } from 'elasticsearch'; -import { IndicesValidateQueryParams } from 'elasticsearch'; -import { InfoParams } from 'elasticsearch'; -import { IngestDeletePipelineParams } from 'elasticsearch'; -import { IngestGetPipelineParams } from 'elasticsearch'; -import { IngestPutPipelineParams } from 'elasticsearch'; -import { IngestSimulateParams } from 'elasticsearch'; +import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType } from 'src/core/server/kibana_config'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; import { MaybePromise } from '@kbn/utility-types'; -import { MGetParams } from 'elasticsearch'; -import { MGetResponse } from 'elasticsearch'; -import { MSearchParams } from 'elasticsearch'; -import { MSearchResponse } from 'elasticsearch'; -import { MSearchTemplateParams } from 'elasticsearch'; -import { MTermVectorsParams } from 'elasticsearch'; -import { NodesHotThreadsParams } from 'elasticsearch'; -import { NodesInfoParams } from 'elasticsearch'; -import { NodesStatsParams } from 'elasticsearch'; import { Observable } from 'rxjs'; import { ParsedQuery } from 'query-string'; import { Path } from 'history'; -import { PingParams } from 'elasticsearch'; import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/server/types'; -import { PutScriptParams } from 'elasticsearch'; -import { PutTemplateParams } from 'elasticsearch'; import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; -import { ReindexParams } from 'elasticsearch'; -import { ReindexRethrottleParams } from 'elasticsearch'; -import { RenderSearchTemplateParams } from 'elasticsearch'; import * as Rx from 'rxjs'; -import { ScrollParams } from 'elasticsearch'; -import { SearchParams } from 'elasticsearch'; -import { SearchResponse } from 'elasticsearch'; -import { SearchShardsParams } from 'elasticsearch'; -import { SearchTemplateParams } from 'elasticsearch'; import { ShallowPromise } from '@kbn/utility-types'; -import { SnapshotCreateParams } from 'elasticsearch'; -import { SnapshotCreateRepositoryParams } from 'elasticsearch'; -import { SnapshotDeleteParams } from 'elasticsearch'; -import { SnapshotDeleteRepositoryParams } from 'elasticsearch'; -import { SnapshotGetParams } from 'elasticsearch'; -import { SnapshotGetRepositoryParams } from 'elasticsearch'; -import { SnapshotRestoreParams } from 'elasticsearch'; -import { SnapshotStatusParams } from 'elasticsearch'; -import { SnapshotVerifyRepositoryParams } from 'elasticsearch'; -import { SuggestParams } from 'elasticsearch'; -import { TasksCancelParams } from 'elasticsearch'; -import { TasksGetParams } from 'elasticsearch'; -import { TasksListParams } from 'elasticsearch'; -import { TermvectorsParams } from 'elasticsearch'; +import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; +import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; +import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UnregisterCallback } from 'history'; -import { UpdateDocumentByQueryParams } from 'elasticsearch'; -import { UpdateDocumentParams } from 'elasticsearch'; import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; // @internal (undocumented) diff --git a/src/core/server/elasticsearch/client/types.ts b/src/core/server/elasticsearch/client/types.ts index 285f52e89a591..827b185672c7c 100644 --- a/src/core/server/elasticsearch/client/types.ts +++ b/src/core/server/elasticsearch/client/types.ts @@ -42,34 +42,50 @@ export type ElasticsearchClient = Omit< }; }; -interface ShardsResponse { +/** + * All response typings are maintained until elasticsearch-js provides them out of the box + * https://github.com/elastic/elasticsearch-js/pull/970 + */ + +/** + * @public + */ +export interface ShardsResponse { total: number; successful: number; failed: number; skipped: number; } -interface Explanation { +/** + * @public + */ +export interface Explanation { value: number; description: string; details: Explanation[]; } -interface ShardsInfo { +/** + * @public + */ +export interface ShardsInfo { total: number; successful: number; skipped: number; failed: number; } +/** + * @public + */ export interface CountResponse { _shards: ShardsInfo; count: number; } /** - * Maintained until elasticsearch provides response typings out of the box - * https://github.com/elastic/elasticsearch-js/pull/970 + * @public */ export interface SearchResponse { took: number; @@ -97,6 +113,9 @@ export interface SearchResponse { aggregations?: any; } +/** + * @public + */ export interface GetResponse { _index: string; _type: string; @@ -109,6 +128,9 @@ export interface GetResponse { _primary_term: number; } +/** + * @public + */ export interface DeleteDocumentResponse { _shards: ShardsResponse; found: boolean; diff --git a/src/core/server/elasticsearch/index.ts b/src/core/server/elasticsearch/index.ts index 32be6e6bf34dd..9359b88434396 100644 --- a/src/core/server/elasticsearch/index.ts +++ b/src/core/server/elasticsearch/index.ts @@ -36,8 +36,12 @@ export { ElasticsearchClientConfig, ElasticsearchClient, IScopedClusterClient, + // responses SearchResponse, + CountResponse, + ShardsInfo, + ShardsResponse, + Explanation, GetResponse, DeleteDocumentResponse, - CountResponse, } from './client'; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index c846e81573acb..f46b41d6b8793 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -110,6 +110,13 @@ export { FakeRequest, ScopeableRequest, ElasticsearchClient, + SearchResponse, + CountResponse, + ShardsInfo, + ShardsResponse, + Explanation, + GetResponse, + DeleteDocumentResponse, } from './elasticsearch'; export * from './elasticsearch/legacy/api_types'; export { diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 4b6bcbc8ad7a0..bb4f2f30ac18f 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -45,7 +45,7 @@ import { ExplainParams } from 'elasticsearch'; import { FieldStatsParams } from 'elasticsearch'; import { GenericParams } from 'elasticsearch'; import { GetParams } from 'elasticsearch'; -import { GetResponse } from 'elasticsearch'; +import { GetResponse as GetResponse_2 } from 'elasticsearch'; import { GetScriptParams } from 'elasticsearch'; import { GetSourceParams } from 'elasticsearch'; import { GetTemplateParams } from 'elasticsearch'; @@ -121,7 +121,7 @@ import { ResponseToolkit } from 'hapi'; import { SchemaTypeError } from '@kbn/config-schema'; import { ScrollParams } from 'elasticsearch'; import { SearchParams } from 'elasticsearch'; -import { SearchResponse } from 'elasticsearch'; +import { SearchResponse as SearchResponse_2 } from 'elasticsearch'; import { SearchShardsParams } from 'elasticsearch'; import { SearchTemplateParams } from 'elasticsearch'; import { Server } from 'hapi'; @@ -532,6 +532,14 @@ export interface CoreStatus { savedObjects: ServiceStatus; } +// @public (undocumented) +export interface CountResponse { + // (undocumented) + count: number; + // (undocumented) + _shards: ShardsInfo; +} + // @public export class CspConfig implements ICspConfig { // @internal @@ -592,6 +600,28 @@ export const DEFAULT_APP_CATEGORIES: Readonly<{ }; }>; +// @public (undocumented) +export interface DeleteDocumentResponse { + // (undocumented) + error?: { + type: string; + }; + // (undocumented) + found: boolean; + // (undocumented) + _id: string; + // (undocumented) + _index: string; + // (undocumented) + result: string; + // (undocumented) + _shards: ShardsResponse; + // (undocumented) + _type: string; + // (undocumented) + _version: number; +} + // @public (undocumented) export interface DeprecationAPIClientParams extends GenericParams { // (undocumented) @@ -642,6 +672,13 @@ export interface DiscoveredPlugin { readonly requiredPlugins: readonly PluginName[]; } +// @public +export type ElasticsearchClient = Omit & { + transport: { + request(params: TransportRequestParams, options?: TransportRequestOptions): TransportRequestPromise; + }; +}; + // @public export class ElasticsearchConfig { constructor(rawConfig: ElasticsearchConfigType); @@ -709,6 +746,16 @@ export interface ErrorHttpResponseOptions { headers?: ResponseHeaders; } +// @public (undocumented) +export interface Explanation { + // (undocumented) + description: string; + // (undocumented) + details: Explanation[]; + // (undocumented) + value: number; +} + // @public export function exportSavedObjectsToStream({ types, objects, search, savedObjectsClient, exportSizeLimit, includeReferencesDeep, excludeExportDetails, namespace, }: SavedObjectsExportOptions): Promise; @@ -736,6 +783,28 @@ export function getFlattenedObject(rootValue: Record): { [key: string]: any; }; +// @public (undocumented) +export interface GetResponse { + // (undocumented) + found: boolean; + // (undocumented) + _id: string; + // (undocumented) + _index: string; + // (undocumented) + _primary_term: number; + // (undocumented) + _routing?: string; + // (undocumented) + _seq_no: number; + // (undocumented) + _source: T; + // (undocumented) + _type: string; + // (undocumented) + _version: number; +} + // @public export type HandlerContextType> = T extends HandlerFunction ? U : never; @@ -1042,7 +1111,7 @@ export interface LegacyAPICaller { // (undocumented) (endpoint: 'fieldStats', params: FieldStatsParams, options?: LegacyCallAPIOptions): ReturnType; // (undocumented) - (endpoint: 'get', params: GetParams, options?: LegacyCallAPIOptions): Promise>; + (endpoint: 'get', params: GetParams, options?: LegacyCallAPIOptions): Promise>; // (undocumented) (endpoint: 'getScript', params: GetScriptParams, options?: LegacyCallAPIOptions): ReturnType; // (undocumented) @@ -1074,9 +1143,9 @@ export interface LegacyAPICaller { // (undocumented) (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: LegacyCallAPIOptions): ReturnType; // (undocumented) - (endpoint: 'scroll', params: ScrollParams, options?: LegacyCallAPIOptions): Promise>; + (endpoint: 'scroll', params: ScrollParams, options?: LegacyCallAPIOptions): Promise>; // (undocumented) - (endpoint: 'search', params: SearchParams, options?: LegacyCallAPIOptions): Promise>; + (endpoint: 'search', params: SearchParams, options?: LegacyCallAPIOptions): Promise>; // (undocumented) (endpoint: 'searchShards', params: SearchShardsParams, options?: LegacyCallAPIOptions): ReturnType; // (undocumented) @@ -2082,7 +2151,7 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { // @public (undocumented) export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { - refresh?: MutatingOperationRefreshSetting; + refresh?: boolean; } // @public (undocumented) @@ -2396,7 +2465,7 @@ export class SavedObjectsRepository { // Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts // // @internal - static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, callCluster: LegacyAPICaller, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; + static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<{}>; @@ -2412,7 +2481,7 @@ export class SavedObjectsRepository { attributes: any; }>; update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; - } +} // @public export interface SavedObjectsRepositoryFactory { @@ -2552,6 +2621,39 @@ export type SavedObjectUnsanitizedDoc = SavedObjectDoc & Partial // @public export type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest; +// @public (undocumented) +export interface SearchResponse { + // (undocumented) + aggregations?: any; + // (undocumented) + hits: { + total: number; + max_score: number; + hits: Array<{ + _index: string; + _type: string; + _id: string; + _score: number; + _source: T; + _version?: number; + _explanation?: Explanation; + fields?: any; + highlight?: any; + inner_hits?: any; + matched_queries?: string[]; + sort?: string[]; + }>; + }; + // (undocumented) + _scroll_id?: string; + // (undocumented) + _shards: ShardsResponse; + // (undocumented) + timed_out: boolean; + // (undocumented) + took: number; +} + // @public export interface ServiceStatus | unknown = unknown> { detail?: string; @@ -2612,6 +2714,30 @@ export interface SessionStorageFactory { asScoped: (request: KibanaRequest) => SessionStorage; } +// @public (undocumented) +export interface ShardsInfo { + // (undocumented) + failed: number; + // (undocumented) + skipped: number; + // (undocumented) + successful: number; + // (undocumented) + total: number; +} + +// @public (undocumented) +export interface ShardsResponse { + // (undocumented) + failed: number; + // (undocumented) + skipped: number; + // (undocumented) + successful: number; + // (undocumented) + total: number; +} + // @public (undocumented) export type SharedGlobalConfig = RecursiveReadonly<{ kibana: Pick; diff --git a/src/dev/run_check_published_api_changes.ts b/src/dev/run_check_published_api_changes.ts index 0aa450c8b002a..28e8570812915 100644 --- a/src/dev/run_check_published_api_changes.ts +++ b/src/dev/run_check_published_api_changes.ts @@ -17,8 +17,6 @@ * under the License. */ -/* eslint-disable no-console */ - import { ToolingLog } from '@kbn/dev-utils'; import { Extractor, @@ -35,6 +33,11 @@ import fs from 'fs'; import path from 'path'; import getopts from 'getopts'; +const log = new ToolingLog({ + level: 'info', + writeTo: process.stdout, +}); + /* * Step 1: execute build:types * This users tsconfig.types.json to generate types in `target/types` @@ -92,13 +95,13 @@ const apiExtractorConfig = (folder: string): ExtractorConfig => { }, }, }; - const con = ExtractorConfig.prepare({ + const cfg = ExtractorConfig.prepare({ configObject: config, configObjectFullPath: undefined, packageJsonFullPath: path.resolve('package.json'), }); - return con; + return cfg; }; const runBuildTypes = async () => { @@ -108,7 +111,7 @@ const runBuildTypes = async () => { const runApiDocumenter = async (folder: string) => { const sourceFolder = `./build/${folder}`; const targetFolder = `./docs/development/${folder}`; - console.log(`Generating docs from ${sourceFolder} into ${targetFolder}...`); + log.info(`Generating docs from ${sourceFolder} into ${targetFolder}...`); await execa('api-documenter', ['generate', '-i', sourceFolder, '-o', targetFolder], { preferLocal: true, }); @@ -117,7 +120,7 @@ const runApiDocumenter = async (folder: string) => { const renameExtractedApiPackageName = async (folder: string) => { const fname = getReportFileName(folder); const jsonApiFile = `build/${folder}/${fname}.api.json`; - console.log(`Updating ${jsonApiFile}...`); + log.info(`Updating ${jsonApiFile}...`); const json = JSON.parse(fs.readFileSync(jsonApiFile).toString()); json.name = json.canonicalReference = `kibana-plugin-${folder.replace(/\//g, '-')}`; fs.writeFileSync(jsonApiFile, JSON.stringify(json, null, 2)); @@ -127,11 +130,7 @@ const renameExtractedApiPackageName = async (folder: string) => { * Runs api-extractor with a custom logger in order to extract results from the process * */ -const runApiExtractor = ( - log: ToolingLog, - folder: string, - acceptChanges: boolean = false -): ExtractorResult => { +const runApiExtractor = (folder: string, acceptChanges: boolean = false): ExtractorResult => { const config = apiExtractorConfig(folder); const options = { // Indicates that API Extractor is running as part of a local build, @@ -177,13 +176,10 @@ interface Options { filter: string; } -async function run( - folder: string, - { log, opts }: { log: ToolingLog; opts: Options } -): Promise { +async function run(folder: string, { opts }: { opts: Options }): Promise { log.info(`${folder} API: checking for changes in API signature...`); - const { apiReportChanged, succeeded } = runApiExtractor(log, folder, opts.accept); + const { apiReportChanged, succeeded } = runApiExtractor(folder, opts.accept); // If we're not accepting changes and there's a failure, exit. if (!opts.accept && !succeeded) { @@ -209,11 +205,6 @@ async function run( } (async () => { - const log = new ToolingLog({ - level: 'info', - writeTo: process.stdout, - }); - const extraFlags: string[] = []; const opts = (getopts(process.argv.slice(2), { boolean: ['accept', 'docs', 'help'], @@ -276,26 +267,22 @@ async function run( return !(extraFlags.length > 0); } - try { - log.info(`Building types for api extractor...`); - await runBuildTypes(); - } catch (e) { - log.error(e); - return false; - } + log.info('Building types for api extractor...'); + await runBuildTypes(); + log.info('Types for api extractor has been built'); const filteredFolders = folders.filter((folder) => opts.filter.length ? folder.match(opts.filter) : true ); const results = []; for (const folder of filteredFolders) { - results.push(await run(folder, { log, opts })); + results.push(await run(folder, { opts })); } if (results.includes(false)) { process.exitCode = 1; } })().catch((e) => { - console.log(e); + log.error(e); process.exitCode = 1; }); diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 65670bc1cf83e..2b904ed9536e0 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -7,44 +7,15 @@ import { $Values } from '@kbn/utility-types'; import _ from 'lodash'; import { Action } from 'history'; +import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import { ApplicationStart } from 'kibana/public'; import { Assign } from '@kbn/utility-types'; import { BehaviorSubject } from 'rxjs'; import Boom from 'boom'; -import { BulkIndexDocumentsParams } from 'elasticsearch'; -import { CatAliasesParams } from 'elasticsearch'; -import { CatAllocationParams } from 'elasticsearch'; -import { CatCommonParams } from 'elasticsearch'; -import { CatFielddataParams } from 'elasticsearch'; -import { CatHealthParams } from 'elasticsearch'; -import { CatHelpParams } from 'elasticsearch'; -import { CatIndicesParams } from 'elasticsearch'; -import { CatRecoveryParams } from 'elasticsearch'; -import { CatSegmentsParams } from 'elasticsearch'; -import { CatShardsParams } from 'elasticsearch'; -import { CatSnapshotsParams } from 'elasticsearch'; -import { CatTasksParams } from 'elasticsearch'; -import { CatThreadPoolParams } from 'elasticsearch'; -import { ClearScrollParams } from 'elasticsearch'; -import { Client } from 'elasticsearch'; -import { ClusterAllocationExplainParams } from 'elasticsearch'; -import { ClusterGetSettingsParams } from 'elasticsearch'; -import { ClusterHealthParams } from 'elasticsearch'; -import { ClusterPendingTasksParams } from 'elasticsearch'; -import { ClusterPutSettingsParams } from 'elasticsearch'; -import { ClusterRerouteParams } from 'elasticsearch'; -import { ClusterStateParams } from 'elasticsearch'; -import { ClusterStatsParams } from 'elasticsearch'; import { Component } from 'react'; import { CoreSetup } from 'src/core/public'; import { CoreStart } from 'kibana/public'; import { CoreStart as CoreStart_2 } from 'src/core/public'; -import { CountParams } from 'elasticsearch'; -import { CreateDocumentParams } from 'elasticsearch'; -import { DeleteDocumentByQueryParams } from 'elasticsearch'; -import { DeleteDocumentParams } from 'elasticsearch'; -import { DeleteScriptParams } from 'elasticsearch'; -import { DeleteTemplateParams } from 'elasticsearch'; import { Ensure } from '@kbn/utility-types'; import { ErrorToastOptions } from 'src/core/public/notifications'; import { EuiBreadcrumb } from '@elastic/eui'; @@ -53,98 +24,33 @@ import { EuiComboBoxProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; import { EuiGlobalToastListToast } from '@elastic/eui'; import { ExclusiveUnion } from '@elastic/eui'; -import { ExistsParams } from 'elasticsearch'; -import { ExplainParams } from 'elasticsearch'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; import { ExpressionsSetup } from 'src/plugins/expressions/public'; -import { FieldStatsParams } from 'elasticsearch'; -import { GenericParams } from 'elasticsearch'; -import { GetParams } from 'elasticsearch'; -import { GetResponse } from 'elasticsearch'; -import { GetScriptParams } from 'elasticsearch'; -import { GetSourceParams } from 'elasticsearch'; -import { GetTemplateParams } from 'elasticsearch'; import { History } from 'history'; import { Href } from 'history'; import { IconType } from '@elastic/eui'; -import { IndexDocumentParams } from 'elasticsearch'; -import { IndicesAnalyzeParams } from 'elasticsearch'; -import { IndicesClearCacheParams } from 'elasticsearch'; -import { IndicesCloseParams } from 'elasticsearch'; -import { IndicesCreateParams } from 'elasticsearch'; -import { IndicesDeleteAliasParams } from 'elasticsearch'; -import { IndicesDeleteParams } from 'elasticsearch'; -import { IndicesDeleteTemplateParams } from 'elasticsearch'; -import { IndicesExistsAliasParams } from 'elasticsearch'; -import { IndicesExistsParams } from 'elasticsearch'; -import { IndicesExistsTemplateParams } from 'elasticsearch'; -import { IndicesExistsTypeParams } from 'elasticsearch'; -import { IndicesFlushParams } from 'elasticsearch'; -import { IndicesFlushSyncedParams } from 'elasticsearch'; -import { IndicesForcemergeParams } from 'elasticsearch'; -import { IndicesGetAliasParams } from 'elasticsearch'; -import { IndicesGetFieldMappingParams } from 'elasticsearch'; -import { IndicesGetMappingParams } from 'elasticsearch'; -import { IndicesGetParams } from 'elasticsearch'; -import { IndicesGetSettingsParams } from 'elasticsearch'; -import { IndicesGetTemplateParams } from 'elasticsearch'; -import { IndicesGetUpgradeParams } from 'elasticsearch'; -import { IndicesOpenParams } from 'elasticsearch'; -import { IndicesPutAliasParams } from 'elasticsearch'; -import { IndicesPutMappingParams } from 'elasticsearch'; -import { IndicesPutSettingsParams } from 'elasticsearch'; -import { IndicesPutTemplateParams } from 'elasticsearch'; -import { IndicesRecoveryParams } from 'elasticsearch'; -import { IndicesRefreshParams } from 'elasticsearch'; -import { IndicesRolloverParams } from 'elasticsearch'; -import { IndicesSegmentsParams } from 'elasticsearch'; -import { IndicesShardStoresParams } from 'elasticsearch'; -import { IndicesShrinkParams } from 'elasticsearch'; -import { IndicesStatsParams } from 'elasticsearch'; -import { IndicesUpdateAliasesParams } from 'elasticsearch'; -import { IndicesUpgradeParams } from 'elasticsearch'; -import { IndicesValidateQueryParams } from 'elasticsearch'; -import { InfoParams } from 'elasticsearch'; -import { IngestDeletePipelineParams } from 'elasticsearch'; -import { IngestGetPipelineParams } from 'elasticsearch'; -import { IngestPutPipelineParams } from 'elasticsearch'; -import { IngestSimulateParams } from 'elasticsearch'; import { InjectedIntl } from '@kbn/i18n/react'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IUiSettingsClient } from 'src/core/public'; import { IUiSettingsClient as IUiSettingsClient_3 } from 'kibana/public'; +import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType } from 'src/core/server/kibana_config'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; import { MaybePromise } from '@kbn/utility-types'; import { METRIC_TYPE } from '@kbn/analytics'; -import { MGetParams } from 'elasticsearch'; -import { MGetResponse } from 'elasticsearch'; import { Moment } from 'moment'; import moment from 'moment'; -import { MSearchParams } from 'elasticsearch'; -import { MSearchResponse } from 'elasticsearch'; -import { MSearchTemplateParams } from 'elasticsearch'; -import { MTermVectorsParams } from 'elasticsearch'; import { NameList } from 'elasticsearch'; -import { NodesHotThreadsParams } from 'elasticsearch'; -import { NodesInfoParams } from 'elasticsearch'; -import { NodesStatsParams } from 'elasticsearch'; import { Observable } from 'rxjs'; import { Path } from 'history'; -import { PingParams } from 'elasticsearch'; import { Plugin as Plugin_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import { PopoverAnchorPosition } from '@elastic/eui'; import { PublicUiSettingsParams } from 'src/core/server/types'; -import { PutScriptParams } from 'elasticsearch'; -import { PutTemplateParams } from 'elasticsearch'; import React from 'react'; import * as React_2 from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; -import { ReindexParams } from 'elasticsearch'; -import { ReindexRethrottleParams } from 'elasticsearch'; -import { RenderSearchTemplateParams } from 'elasticsearch'; import { Reporter } from '@kbn/analytics'; import { RequestAdapter } from 'src/plugins/inspector/common'; import { RequestStatistics } from 'src/plugins/inspector/common'; @@ -153,38 +59,22 @@ import * as Rx from 'rxjs'; import { SavedObject } from 'src/core/server'; import { SavedObject as SavedObject_3 } from 'src/core/public'; import { SavedObjectsClientContract } from 'src/core/public'; -import { ScrollParams } from 'elasticsearch'; import { SearchParams } from 'elasticsearch'; import { SearchResponse as SearchResponse_2 } from 'elasticsearch'; -import { SearchShardsParams } from 'elasticsearch'; -import { SearchTemplateParams } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; -import { SnapshotCreateParams } from 'elasticsearch'; -import { SnapshotCreateRepositoryParams } from 'elasticsearch'; -import { SnapshotDeleteParams } from 'elasticsearch'; -import { SnapshotDeleteRepositoryParams } from 'elasticsearch'; -import { SnapshotGetParams } from 'elasticsearch'; -import { SnapshotGetRepositoryParams } from 'elasticsearch'; -import { SnapshotRestoreParams } from 'elasticsearch'; -import { SnapshotStatusParams } from 'elasticsearch'; -import { SnapshotVerifyRepositoryParams } from 'elasticsearch'; import { Subscription } from 'rxjs'; -import { SuggestParams } from 'elasticsearch'; -import { TasksCancelParams } from 'elasticsearch'; -import { TasksGetParams } from 'elasticsearch'; -import { TasksListParams } from 'elasticsearch'; -import { TermvectorsParams } from 'elasticsearch'; import { Toast } from 'kibana/public'; import { ToastInputFields } from 'src/core/public/notifications'; import { ToastsStart } from 'kibana/public'; +import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; +import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; +import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; import { TypeOf } from '@kbn/config-schema'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { Unit } from '@elastic/datemath'; import { UnregisterCallback } from 'history'; import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; -import { UpdateDocumentByQueryParams } from 'elasticsearch'; -import { UpdateDocumentParams } from 'elasticsearch'; import { UserProvidedValues } from 'src/core/server/types'; // Warning: (ae-forgotten-export) The symbol "AggConfigSerialized" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 99a77ff9aeb10..7ad2f9edd3325 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -4,6 +4,7 @@ ```ts +import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import Boom from 'boom'; import { BulkIndexDocumentsParams } from 'elasticsearch'; import { CatAliasesParams } from 'elasticsearch'; @@ -91,6 +92,7 @@ import { IngestDeletePipelineParams } from 'elasticsearch'; import { IngestGetPipelineParams } from 'elasticsearch'; import { IngestPutPipelineParams } from 'elasticsearch'; import { IngestSimulateParams } from 'elasticsearch'; +import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType as KibanaConfigType_2 } from 'src/core/server/kibana_config'; import { KibanaRequest as KibanaRequest_2 } from 'kibana/server'; import { LegacyAPICaller as LegacyAPICaller_2 } from 'kibana/server'; @@ -143,6 +145,9 @@ import { TasksGetParams } from 'elasticsearch'; import { TasksListParams } from 'elasticsearch'; import { TermvectorsParams } from 'elasticsearch'; import { ToastInputFields } from 'src/core/public/notifications'; +import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; +import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; +import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { Unit } from '@elastic/datemath'; From 8648063b1073cc04d308dd83ca2b81d8fe1e2120 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Tue, 28 Jul 2020 10:52:28 -0400 Subject: [PATCH 44/75] [CI] Harden Slack notifications (#73361) --- .../src/test/KibanaBasePipelineTest.groovy | 4 ++ .../src/test/slackNotifications.groovy | 63 +++++++++++++++++++ vars/slackNotifications.groovy | 50 +++++++++++++-- 3 files changed, 112 insertions(+), 5 deletions(-) diff --git a/.ci/pipeline-library/src/test/KibanaBasePipelineTest.groovy b/.ci/pipeline-library/src/test/KibanaBasePipelineTest.groovy index 086484f2385b0..4112cb3c0e14b 100644 --- a/.ci/pipeline-library/src/test/KibanaBasePipelineTest.groovy +++ b/.ci/pipeline-library/src/test/KibanaBasePipelineTest.groovy @@ -78,6 +78,10 @@ class KibanaBasePipelineTest extends BasePipelineTest { return helper.callStack.find { it.methodName == name } } + def fnMocks(String name) { + helper.callStack.findAll { it.methodName == name } + } + void mockFailureBuild() { props([ buildUtils: [ diff --git a/.ci/pipeline-library/src/test/slackNotifications.groovy b/.ci/pipeline-library/src/test/slackNotifications.groovy index 467e4a0e5f520..f7e39f5fad903 100644 --- a/.ci/pipeline-library/src/test/slackNotifications.groovy +++ b/.ci/pipeline-library/src/test/slackNotifications.groovy @@ -59,4 +59,67 @@ class SlackNotificationsTest extends KibanaBasePipelineTest { args.blocks[2].text.text.toString() ) } + + @Test + void 'sendFailedBuild() should call slackSend() with a backup message when first attempt fails'() { + mockFailureBuild() + def counter = 0 + helper.registerAllowedMethod('slackSend', [Map.class], { ++counter > 1 }) + slackNotifications.sendFailedBuild() + + def args = fnMocks('slackSend')[1].args[0] + + def expected = [ + channel: '#kibana-operations-alerts', + username: 'Kibana Operations', + iconEmoji: ':jenkins:', + color: 'danger', + message: ':broken_heart: elastic / kibana # master #1', + ] + + expected.each { + assertEquals(it.value.toString(), args[it.key].toString()) + } + + assertEquals( + ":broken_heart: **" + + "\n\nFirst attempt at sending this notification failed. Please check the build.", + args.blocks[0].text.text.toString() + ) + } + + @Test + void 'getTestFailures() should truncate list of failures to 10'() { + prop('testUtils', [ + getFailures: { + return (1..12).collect { + return [ + url: Mocks.TEST_FAILURE_URL, + fullDisplayName: "Failure #${it}", + ] + } + }, + ]) + + def message = (String) slackNotifications.getTestFailures() + + assertTrue("Message ends with truncated indicator", message.endsWith("...and 2 more")) + assertTrue("Message contains Failure #10", message.contains("Failure #10")) + assertTrue("Message does not contain Failure #11", !message.contains("Failure #11")) + } + + @Test + void 'shortenMessage() should truncate a long message, but leave parts that fit'() { + assertEquals('Hello\nHello\n[...truncated...]', slackNotifications.shortenMessage('Hello\nHello\nthis is a long string', 29)) + } + + @Test + void 'shortenMessage() should not modify a short message'() { + assertEquals('Hello world', slackNotifications.shortenMessage('Hello world', 11)) + } + + @Test + void 'shortenMessage() should truncate an entire message with only one part'() { + assertEquals('[...truncated...]', slackNotifications.shortenMessage('Hello world this is a really long message', 40)) + } } diff --git a/vars/slackNotifications.groovy b/vars/slackNotifications.groovy index 2ffb420ecf3f4..30f86e6d6f0ad 100644 --- a/vars/slackNotifications.groovy +++ b/vars/slackNotifications.groovy @@ -13,12 +13,35 @@ def dividerBlock() { return [ type: "divider" ] } +// If a message is longer than the limit, split it up by '\n' into parts, and return as many parts as will fit within the limit +def shortenMessage(message, sizeLimit = 3000) { + if (message.size() <= sizeLimit) { + return message + } + + def truncatedMessage = "[...truncated...]" + + def parts = message.split("\n") + message = "" + + for(def part in parts) { + if ((message.size() + part.size() + truncatedMessage.size() + 1) > sizeLimit) { + break; + } + message += part+"\n" + } + + message += truncatedMessage + + return message.size() <= sizeLimit ? message : truncatedMessage +} + def markdownBlock(message) { return [ type: "section", text: [ type: "mrkdwn", - text: message, + text: shortenMessage(message, 3000), // 3000 is max text length for `section`s only ], ] } @@ -29,7 +52,7 @@ def contextBlock(message) { elements: [ [ type: 'mrkdwn', - text: message, + text: message, // Not sure what the size limit is here, I tried 10000s of characters and it still worked ] ] ] @@ -62,7 +85,7 @@ def getTestFailures() { def messages = [] messages << "*Test Failures*" - def list = failures.collect { + def list = failures.take(10).collect { def name = it .fullDisplayName .split(/\./, 2)[-1] @@ -73,7 +96,9 @@ def getTestFailures() { return "• <${it.url}|${name}>" }.join("\n") - return "*Test Failures*\n${list}" + + def moreText = failures.size() > 10 ? "\n• ...and ${failures.size()-10} more" : "" + return "*Test Failures*\n${list}${moreText}" } def getDefaultDisplayName() { @@ -98,6 +123,10 @@ def getStatusIcon() { return ':broken_heart:' } +def getBackupMessage(config) { + return "${getStatusIcon()} ${config.title}\n\nFirst attempt at sending this notification failed. Please check the build." +} + def sendFailedBuild(Map params = [:]) { def config = [ channel: '#kibana-operations-alerts', @@ -117,7 +146,7 @@ def sendFailedBuild(Map params = [:]) { blocks << dividerBlock() blocks << config.context - slackSend( + def resp = slackSend( channel: config.channel, username: config.username, iconEmoji: config.icon, @@ -125,6 +154,17 @@ def sendFailedBuild(Map params = [:]) { message: message, blocks: blocks ) + + if (!resp) { + slackSend( + channel: config.channel, + username: config.username, + iconEmoji: config.icon, + color: config.color, + message: message, + blocks: [markdownBlock(getBackupMessage(config))] + ) + } } def onFailure(Map options = [:]) { From 0149c65221aac101043eaa03b63b4e223801ec1d Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Tue, 28 Jul 2020 10:56:29 -0400 Subject: [PATCH 45/75] lodash `4.17.15` -> `4.17.19` (#73122) --- yarn.lock | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index c1328731db150..ee4188440e0ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20737,7 +20737,12 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.11, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.16, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + +lodash@4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== From 3d5d4de63ce456e734fa1c5d6fdf28e779c46a24 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 28 Jul 2020 08:01:34 -0700 Subject: [PATCH 46/75] [kbn/optimizer] log info about the metrics being reported even when reporter is disabled (#73389) Co-authored-by: spalger --- .../ci_stats_reporter/ci_stats_reporter.ts | 4 +++- packages/kbn-optimizer/src/cli.ts | 2 +- .../kbn-optimizer/src/log_optimizer_state.ts | 6 ++--- .../src/report_optimizer_stats.ts | 22 ++++++++++++++++--- .../tasks/build_kibana_platform_plugins.ts | 2 +- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts index b38a27fdc1b48..b0378ab6c5cd5 100644 --- a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts +++ b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts @@ -29,6 +29,8 @@ interface Config { buildId: string; } +export type CiStatsMetrics = Array<{ group: string; id: string; value: number }>; + function parseConfig(log: ToolingLog) { const configJson = process.env.KIBANA_CI_STATS_CONFIG; if (!configJson) { @@ -84,7 +86,7 @@ export class CiStatsReporter { return !!this.config; } - async metrics(metrics: Array<{ group: string; id: string; value: number }>) { + async metrics(metrics: CiStatsMetrics) { if (!this.config) { return; } diff --git a/packages/kbn-optimizer/src/cli.ts b/packages/kbn-optimizer/src/cli.ts index 9d3f4b88a258f..542dc7255f22f 100644 --- a/packages/kbn-optimizer/src/cli.ts +++ b/packages/kbn-optimizer/src/cli.ts @@ -116,7 +116,7 @@ run( log.warning('Unable to initialize CiStatsReporter from env'); } - update$ = update$.pipe(reportOptimizerStats(reporter, config)); + update$ = update$.pipe(reportOptimizerStats(reporter, config, log)); } await update$.pipe(logOptimizerState(log, config)).toPromise(); diff --git a/packages/kbn-optimizer/src/log_optimizer_state.ts b/packages/kbn-optimizer/src/log_optimizer_state.ts index 20d98f74dbe86..e8bc6debf971e 100644 --- a/packages/kbn-optimizer/src/log_optimizer_state.ts +++ b/packages/kbn-optimizer/src/log_optimizer_state.ts @@ -104,7 +104,7 @@ export function logOptimizerState(log: ToolingLog, config: OptimizerConfig) { } if (state.phase === 'running' || state.phase === 'initializing') { - return true; + return; } if (state.phase === 'issue') { @@ -119,7 +119,7 @@ export function logOptimizerState(log: ToolingLog, config: OptimizerConfig) { } } log.indent(-4); - return true; + return; } if (state.phase === 'success') { @@ -135,7 +135,7 @@ export function logOptimizerState(log: ToolingLog, config: OptimizerConfig) { ); } - return true; + return; } throw new Error(`unhandled optimizer message: ${inspect(update)}`); diff --git a/packages/kbn-optimizer/src/report_optimizer_stats.ts b/packages/kbn-optimizer/src/report_optimizer_stats.ts index 5057c717efcc3..eff2bce0b827e 100644 --- a/packages/kbn-optimizer/src/report_optimizer_stats.ts +++ b/packages/kbn-optimizer/src/report_optimizer_stats.ts @@ -21,7 +21,7 @@ import Fs from 'fs'; import Path from 'path'; import { materialize, mergeMap, dematerialize } from 'rxjs/operators'; -import { CiStatsReporter } from '@kbn/dev-utils'; +import { CiStatsReporter, CiStatsMetrics, ToolingLog } from '@kbn/dev-utils'; import { OptimizerUpdate$ } from './run_optimizer'; import { OptimizerState, OptimizerConfig } from './optimizer'; @@ -67,7 +67,11 @@ const getFiles = (dir: string, parent?: string) => return true; }); -export function reportOptimizerStats(reporter: CiStatsReporter, config: OptimizerConfig) { +export function reportOptimizerStats( + reporter: CiStatsReporter, + config: OptimizerConfig, + log: ToolingLog +) { return pipeClosure((update$: OptimizerUpdate$) => { let lastState: OptimizerState | undefined; return update$.pipe( @@ -98,10 +102,18 @@ export function reportOptimizerStats(reporter: CiStatsReporter, config: Optimize const miscFiles = outputFiles.filter( (f) => f !== entry && !asyncChunks.includes(f) ); + + if (asyncChunks.length) { + log.verbose(bundle.id, 'async chunks', asyncChunks); + } + if (miscFiles.length) { + log.verbose(bundle.id, 'misc files', asyncChunks); + } + const sumSize = (files: Entry[]) => files.reduce((acc: number, f) => acc + f.stats!.size, 0); - return [ + const metrics: CiStatsMetrics = [ { group: `@kbn/optimizer bundle module count`, id: bundle.id, @@ -123,6 +135,10 @@ export function reportOptimizerStats(reporter: CiStatsReporter, config: Optimize value: sumSize(miscFiles), }, ]; + + log.info(bundle.id, 'metrics', metrics); + + return metrics; }) ) ); diff --git a/src/dev/build/tasks/build_kibana_platform_plugins.ts b/src/dev/build/tasks/build_kibana_platform_plugins.ts index 08637677fcfbe..beb5ad40229df 100644 --- a/src/dev/build/tasks/build_kibana_platform_plugins.ts +++ b/src/dev/build/tasks/build_kibana_platform_plugins.ts @@ -44,7 +44,7 @@ export const BuildKibanaPlatformPlugins: Task = { await runOptimizer(optimizerConfig) .pipe( - reportOptimizerStats(reporter, optimizerConfig), + reportOptimizerStats(reporter, optimizerConfig, log), logOptimizerState(log, optimizerConfig) ) .toPromise(); From f6a53f680552172dced497eb34a20b0f87d0b795 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 28 Jul 2020 11:45:18 -0400 Subject: [PATCH 47/75] Upgrade jimp to v0.14.0 (#73429) --- package.json | 2 +- yarn.lock | 416 +++++++++++++++++++++++++++------------------------ 2 files changed, 223 insertions(+), 195 deletions(-) diff --git a/package.json b/package.json index 0c49ec26be194..51a41cbbab9ff 100644 --- a/package.json +++ b/package.json @@ -460,7 +460,7 @@ "jest-cli": "^25.5.4", "jest-environment-jsdom-thirteen": "^1.0.1", "jest-raw-loader": "^1.0.1", - "jimp": "^0.9.6", + "jimp": "^0.14.0", "json5": "^1.0.1", "license-checker": "^16.0.0", "listr": "^0.14.1", diff --git a/yarn.lock b/yarn.lock index ee4188440e0ca..c31f58c3320e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2767,26 +2767,24 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jimp/bmp@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.9.6.tgz#379e261615d7c1f3b52af4d5a0f324666de53d7d" - integrity sha512-T2Fh/k/eN6cDyOx0KQ4y56FMLo8+mKNhBh7GXMQXLK2NNZ0ckpFo3VHDBZ3HnaFeVTZXF/atLiR9CfnXH+rLxA== +"@jimp/bmp@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.14.0.tgz#6df246026554f276f7b354047c6fff9f5b2b5182" + integrity sha512-5RkX6tSS7K3K3xNEb2ygPuvyL9whjanhoaB/WmmXlJS6ub4DjTqrapu8j4qnIWmO4YYtFeTbDTXV6v9P1yMA5A== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" + "@jimp/utils" "^0.14.0" bmp-js "^0.1.0" - core-js "^3.4.1" -"@jimp/core@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.9.6.tgz#a553f801bd436526d36e8982b99e58e8afc3d17a" - integrity sha512-sQO04S+HZNid68a9ehb4BC2lmW6iZ5JgU9tC+thC2Lhix+N/XKDJcBJ6HevbLgeTzuIAw24C5EKuUeO3C+rE5w== +"@jimp/core@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.14.0.tgz#870c9ca25b40be353ebda1d2abb48723d9010055" + integrity sha512-S62FcKdtLtj3yWsGfJRdFXSutjvHg7aQNiFogMbwq19RP4XJWqS2nOphu7ScB8KrSlyy5nPF2hkWNhLRLyD82w== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" + "@jimp/utils" "^0.14.0" any-base "^1.1.0" buffer "^5.2.0" - core-js "^3.4.1" exif-parser "^0.1.12" file-type "^9.0.0" load-bmfont "^1.3.1" @@ -2795,256 +2793,269 @@ pixelmatch "^4.0.2" tinycolor2 "^1.4.1" -"@jimp/custom@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.9.6.tgz#3d8a19d6ed717f0f1aa3f1b8f42fa374f43bc715" - integrity sha512-ZYKgrBZVoQwvIGlQSO7MFmn7Jn8a9X5g1g+KOTDO9Q0s4vnxdPTtr/qUjG9QYX6zW/6AK4LaIsDinDrrKDnOag== +"@jimp/custom@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.14.0.tgz#1dbbf0094df7403f4e03bc984ed92e7458842f74" + integrity sha512-kQJMeH87+kWJdVw8F9GQhtsageqqxrvzg7yyOw3Tx/s7v5RToe8RnKyMM+kVtBJtNAG+Xyv/z01uYQ2jiZ3GwA== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/core" "^0.9.6" - core-js "^3.4.1" + "@jimp/core" "^0.14.0" -"@jimp/gif@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.9.6.tgz#0a7b1e521daca635b02259f941bdb3600569d8e6" - integrity sha512-Z2muC2On8KHEVrWKCCM0L2eua9kw4bQETzT7gmVsizc8MXAKdS8AyVV9T3ZrImiI0o5UkAN/u0cPi1U2pSiD8Q== +"@jimp/gif@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.14.0.tgz#db159f57c3cfd1566bbe8b124958791998614960" + integrity sha512-DHjoOSfCaCz72+oGGEh8qH0zE6pUBaBxPxxmpYJjkNyDZP7RkbBkZJScIYeQ7BmJxmGN4/dZn+MxamoQlr+UYg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" + gifwrap "^0.9.2" omggif "^1.0.9" -"@jimp/jpeg@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.9.6.tgz#fb90bdc0111966987c5ba59cdca7040be86ead41" - integrity sha512-igSe0pIX3le/CKdvqW4vLXMxoFjTLjEaW6ZHt/h63OegaEa61TzJ2OM7j7DxrEHcMCMlkhUc9Bapk57MAefCTQ== +"@jimp/jpeg@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.14.0.tgz#8a687a6a653bbbae38c522edef8f84bb418d9461" + integrity sha512-561neGbr+87S/YVQYnZSTyjWTHBm9F6F1obYHiyU3wVmF+1CLbxY3FQzt4YolwyQHIBv36Bo0PY2KkkU8BEeeQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" - jpeg-js "^0.3.4" + "@jimp/utils" "^0.14.0" + jpeg-js "^0.4.0" -"@jimp/plugin-blit@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.9.6.tgz#7937e02e3514b95dbe4c70d444054847f6e9ce3c" - integrity sha512-zp7X6uDU1lCu44RaSY88aAvsSKbgqUrfDyWRX1wsamJvvZpRnp1WekWlGyydRtnlUBAGIpiHCHmyh/TJ2I4RWA== +"@jimp/plugin-blit@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.14.0.tgz#5eb374be1201313b2113899fb842232d8fcfd345" + integrity sha512-YoYOrnVHeX3InfgbJawAU601iTZMwEBZkyqcP1V/S33Qnz9uzH1Uj1NtC6fNgWzvX6I4XbCWwtr4RrGFb5CFrw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-blur@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.9.6.tgz#3d74b18c27e9eae11b956ffe26290ece6d250813" - integrity sha512-xEi63hvzewUp7kzw+PI3f9CIrgZbphLI4TDDHWNYuS70RvhTuplbR6RMHD/zFhosrANCkJGr5OZJlrJnsCg6ug== +"@jimp/plugin-blur@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.14.0.tgz#fe07e4932d5a2f5d8c9831e245561553224bfc60" + integrity sha512-9WhZcofLrT0hgI7t0chf7iBQZib//0gJh9WcQMUt5+Q1Bk04dWs8vTgLNj61GBqZXgHSPzE4OpCrrLDBG8zlhQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-color@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.9.6.tgz#d0fca0ed4c2c48fd6f929ef4a03cebf9c1342e14" - integrity sha512-o1HSoqBVUUAsWbqSXnpiHU0atKWy/Q1GUbZ3F5GWt/0OSDyl9RWM82V9axT2vePZHInKjIaimhnx1gGj8bfxkQ== +"@jimp/plugin-circle@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-circle/-/plugin-circle-0.14.0.tgz#82c0e904a34e90fa672fb9c286bc892e92088ddf" + integrity sha512-o5L+wf6QA44tvTum5HeLyLSc5eVfIUd5ZDVi5iRfO4o6GT/zux9AxuTSkKwnjhsG8bn1dDmywAOQGAx7BjrQVA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-color@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.14.0.tgz#772bd2d80a88bc66ea1331d010207870f169a74b" + integrity sha512-JJz512SAILYV0M5LzBb9sbOm/XEj2fGElMiHAxb7aLI6jx+n0agxtHpfpV/AePTLm1vzzDxx6AJxXbKv355hBQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" tinycolor2 "^1.4.1" -"@jimp/plugin-contain@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.9.6.tgz#7d7bbd5e9c2fa4391a3d63620e13a28f51e1e7e8" - integrity sha512-Xz467EN1I104yranET4ff1ViVKMtwKLg1uRe8j3b5VOrjtiXpDbjirNZjP3HTlv8IEUreWNz4BK7ZtfHSptufA== +"@jimp/plugin-contain@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.14.0.tgz#c68115420d182e696f81bbe76fb5e704909b2b6a" + integrity sha512-RX2q233lGyaxiMY6kAgnm9ScmEkNSof0hdlaJAVDS1OgXphGAYAeSIAwzESZN4x3ORaWvkFefeVH9O9/698Evg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-cover@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.9.6.tgz#2853de7f8302f655ae8e95f51ab25a0ed77e3756" - integrity sha512-Ocr27AvtvH4ZT/9EWZgT3+HQV9fG5njwh2CYMHbdpx09O62Asj6pZ4QI0kKzOcux1oLgv59l7a93pEfMOfkfwQ== +"@jimp/plugin-cover@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.14.0.tgz#4755322589c5885e44e14e31b86b542e907297ce" + integrity sha512-0P/5XhzWES4uMdvbi3beUgfvhn4YuQ/ny8ijs5kkYIw6K8mHcl820HahuGpwWMx56DJLHRl1hFhJwo9CeTRJtQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-crop@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.9.6.tgz#82e539af2a2417783abbd143124a57672ff4cc31" - integrity sha512-d9rNdmz3+eYLbSKcTyyp+b8Nmhf6HySnimDXlTej4UP6LDtkq2VAyVaJ12fz9x6dfd8qcXOBXMozSfNCcgpXYA== +"@jimp/plugin-crop@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.14.0.tgz#4cbd856ca84ffc37230fad2534906f2f75aa3057" + integrity sha512-Ojtih+XIe6/XSGtpWtbAXBozhCdsDMmy+THUJAGu2x7ZgKrMS0JotN+vN2YC3nwDpYkM+yOJImQeptSfZb2Sug== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-displace@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.9.6.tgz#67564d081dc6b19007248ca222d025fd6f90c03b" - integrity sha512-SWpbrxiHmUYBVWtDDMjaG3eRDBASrTPaad7l07t73/+kmU6owAKWQW6KtVs05MYSJgXz7Ggdr0fhEn9AYLH1Rg== +"@jimp/plugin-displace@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.14.0.tgz#b0e6a57d00cb1f893f541413fe9d737d23c3b70c" + integrity sha512-c75uQUzMgrHa8vegkgUvgRL/PRvD7paFbFJvzW0Ugs8Wl+CDMGIPYQ3j7IVaQkIS+cAxv+NJ3TIRBQyBrfVEOg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-dither@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.9.6.tgz#dc48669cf51f3933761aa9137e99ebfa000b8cce" - integrity sha512-abm1GjfYK7ru/PoxH9fAUmhl+meHhGEClbVvjjMMe5g2S0BSTvMJl3SrkQD/FMkRLniaS/Qci6aQhIi+8rZmSw== +"@jimp/plugin-dither@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.14.0.tgz#9185ec4c38e02edc9e5831f5d709f6ba891e1b93" + integrity sha512-g8SJqFLyYexXQQsoh4dc1VP87TwyOgeTElBcxSXX2LaaMZezypmxQfLTzOFzZoK8m39NuaoH21Ou1Ftsq7LzVQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-flip@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.9.6.tgz#f81f9b886da8cd56e23dd04d5aa359f2b94f939e" - integrity sha512-KFZTzAzQQ5bct3ii7gysOhWrTKVdUOghkkoSzLi+14nO3uS/dxiu8fPeH1m683ligbdnuM/b22OuLwEwrboTHA== +"@jimp/plugin-fisheye@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-fisheye/-/plugin-fisheye-0.14.0.tgz#9f26346cf2fbc660cc2008cd7fd30a83b5029e78" + integrity sha512-BFfUZ64EikCaABhCA6mR3bsltWhPpS321jpeIQfJyrILdpFsZ/OccNwCgpW1XlbldDHIoNtXTDGn3E+vCE7vDg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-gaussian@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.9.6.tgz#6c93897ee0ff979466184d7d0ec0fbc95c679be4" - integrity sha512-WXKLtJKWchXfWHT5HIOq1HkPKpbH7xBLWPgVRxw00NV/6I8v4xT63A7/Nag78m00JgjwwiE7eK2tLGDbbrPYig== +"@jimp/plugin-flip@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.14.0.tgz#7966d6aa3b5fe1aa4d2d561ff12b8ef5ccb9b071" + integrity sha512-WtL1hj6ryqHhApih+9qZQYA6Ye8a4HAmdTzLbYdTMrrrSUgIzFdiZsD0WeDHpgS/+QMsWwF+NFmTZmxNWqKfXw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-invert@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.9.6.tgz#4b3fa7b81ea976b09b82b3db59ee00ac3093d2fd" - integrity sha512-Pab/cupZrYxeRp07N4L5a4C/3ksTN9k6Knm/o2G5C789OF0rYsGGLcnBR/6h69nPizRZHBYdXCEyXYgujlIFiw== +"@jimp/plugin-gaussian@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.14.0.tgz#452bc1971a4467ad9b984aa67f4c200bf941bb65" + integrity sha512-uaLwQ0XAQoydDlF9tlfc7iD9drYPriFe+jgYnWm8fbw5cN+eOIcnneEX9XCOOzwgLPkNCxGox6Kxjn8zY6GxtQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-mask@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.9.6.tgz#d70be0030ab3294b191f5b487fb655d776820b19" - integrity sha512-ikypRoDJkbxXlo6gW+EZOcTiLDIt0DrPwOFMt1bvL8UV2QPgX+GJ685IYwhIfEhBf/GSNFgB/NYsVvuSufTGeg== +"@jimp/plugin-invert@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.14.0.tgz#cd31a555860e9f821394936d15af161c09c42921" + integrity sha512-UaQW9X9vx8orQXYSjT5VcITkJPwDaHwrBbxxPoDG+F/Zgv4oV9fP+udDD6qmkgI9taU+44Fy+zm/J/gGcMWrdg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-normalize@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.9.6.tgz#c9128412a53485d91236a1da241f3166e572be4a" - integrity sha512-V3GeuAJ1NeL7qsLoDjnypJq24RWDCwbXpKhtxB+Yg9zzgOCkmb041p7ysxbcpkuJsRpKLNABZeNCCqd83bRawA== +"@jimp/plugin-mask@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.14.0.tgz#52619643ac6222f85e6b27dee33c771ca3a6a4c9" + integrity sha512-tdiGM69OBaKtSPfYSQeflzFhEpoRZ+BvKfDEoivyTjauynbjpRiwB1CaiS8En1INTDwzLXTT0Be9SpI3LkJoEA== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-print@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.9.6.tgz#fea31ffeafee18ae7b5cfd6fa131bb205abfee51" - integrity sha512-gKkqZZPQtMSufHOL0mtJm5d/KI2O6+0kUpOBVSYdGedtPXA61kmVnsOd3wwajIMlXA3E0bDxLXLdAguWqjjGgw== +"@jimp/plugin-normalize@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.14.0.tgz#bf39e356b6d473f582ce95633ad49c9cdb82492b" + integrity sha512-AfY8sqlsbbdVwFGcyIPy5JH/7fnBzlmuweb+Qtx2vn29okq6+HelLjw2b+VT2btgGUmWWHGEHd86oRGSoWGyEQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-print@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.14.0.tgz#1c43c2a92a7adc05b464863882cb89ce486d63e6" + integrity sha512-MwP3sH+VS5AhhSTXk7pui+tEJFsxnTKFY3TraFJb8WFbA2Vo2qsRCZseEGwpTLhENB7p/JSsLvWoSSbpmxhFAQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" load-bmfont "^1.4.0" -"@jimp/plugin-resize@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.9.6.tgz#7fb939c8a42e2a3639d661cc7ab24057598693bd" - integrity sha512-r5wJcVII7ZWMuY2l6WSbHPG6gKMFemtCHmJRXGUu+/ZhPGBz3IFluycBpHkWW3OB+jfvuyv1EGQWHU50N1l8Og== +"@jimp/plugin-resize@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.14.0.tgz#ef7fc6c2e45f8bcab62456baf8fd3bc415b02b64" + integrity sha512-qFeMOyXE/Bk6QXN0GQo89+CB2dQcXqoxUcDb2Ah8wdYlKqpi53skABkgVy5pW3EpiprDnzNDboMltdvDslNgLQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-rotate@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.9.6.tgz#06d725155e5cdb615133f57a52f5a860a9d03f3e" - integrity sha512-B2nm/eO2nbvn1DgmnzMd79yt3V6kffhRNrKoo2VKcKFiVze1vGP3MD3fVyw5U1PeqwAFu7oTICFnCf9wKDWSqg== +"@jimp/plugin-rotate@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.14.0.tgz#3632bc159bf1c3b9ec9f459d9c05d02a11781ee7" + integrity sha512-aGaicts44bvpTcq5Dtf93/8TZFu5pMo/61lWWnYmwJJU1RqtQlxbCLEQpMyRhKDNSfPbuP8nyGmaqXlM/82J0Q== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugin-scale@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.9.6.tgz#3fa939c1a4f44383e12afeb7c434eb41f20e4a1c" - integrity sha512-DLsLB5S3mh9+TZY5ycwfLgOJvUcoS7bP0Mi3I8vE1J91qmA+TXoWFFgrIVgnEPw5jSKzNTt8WhykQ0x2lKXncw== +"@jimp/plugin-scale@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.14.0.tgz#d30f0cd1365b8e68f43fa423300ae7f124e9bf10" + integrity sha512-ZcJk0hxY5ZKZDDwflqQNHEGRblgaR+piePZm7dPwPUOSeYEH31P0AwZ1ziceR74zd8N80M0TMft+e3Td6KGBHw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" -"@jimp/plugins@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.9.6.tgz#a1cfdf9f3e1adf5b124686486343888a16c8fd27" - integrity sha512-eQI29e+K+3L/fb5GbPgsBdoftvaYetSOO2RL5z+Gjk6R4EF4QFRo63YcFl+f72Kc1b0JTOoDxClvn/s5GMV0tg== +"@jimp/plugin-shadow@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-shadow/-/plugin-shadow-0.14.0.tgz#471fdb9f109ff2d9e20d533d45e1e18e0b48c749" + integrity sha512-p2igcEr/iGrLiTu0YePNHyby0WYAXM14c5cECZIVnq/UTOOIQ7xIcWZJ1lRbAEPxVVXPN1UibhZAbr3HAb5BjQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-threshold@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-threshold/-/plugin-threshold-0.14.0.tgz#ebd72721c7d1d518c5bb6e494e55d97ac3351d3b" + integrity sha512-N4BlDgm/FoOMV/DQM2rSpzsgqAzkP0DXkWZoqaQrlRxQBo4zizQLzhEL00T/YCCMKnddzgEhnByaocgaaa0fKw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugins@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.14.0.tgz#41dba85f15ab8dadb4162100eb54e5f27b93ee2c" + integrity sha512-vDO3XT/YQlFlFLq5TqNjQkISqjBHT8VMhpWhAfJVwuXIpilxz5Glu4IDLK6jp4IjPR6Yg2WO8TmRY/HI8vLrOw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/plugin-blit" "^0.9.6" - "@jimp/plugin-blur" "^0.9.6" - "@jimp/plugin-color" "^0.9.6" - "@jimp/plugin-contain" "^0.9.6" - "@jimp/plugin-cover" "^0.9.6" - "@jimp/plugin-crop" "^0.9.6" - "@jimp/plugin-displace" "^0.9.6" - "@jimp/plugin-dither" "^0.9.6" - "@jimp/plugin-flip" "^0.9.6" - "@jimp/plugin-gaussian" "^0.9.6" - "@jimp/plugin-invert" "^0.9.6" - "@jimp/plugin-mask" "^0.9.6" - "@jimp/plugin-normalize" "^0.9.6" - "@jimp/plugin-print" "^0.9.6" - "@jimp/plugin-resize" "^0.9.6" - "@jimp/plugin-rotate" "^0.9.6" - "@jimp/plugin-scale" "^0.9.6" - core-js "^3.4.1" + "@jimp/plugin-blit" "^0.14.0" + "@jimp/plugin-blur" "^0.14.0" + "@jimp/plugin-circle" "^0.14.0" + "@jimp/plugin-color" "^0.14.0" + "@jimp/plugin-contain" "^0.14.0" + "@jimp/plugin-cover" "^0.14.0" + "@jimp/plugin-crop" "^0.14.0" + "@jimp/plugin-displace" "^0.14.0" + "@jimp/plugin-dither" "^0.14.0" + "@jimp/plugin-fisheye" "^0.14.0" + "@jimp/plugin-flip" "^0.14.0" + "@jimp/plugin-gaussian" "^0.14.0" + "@jimp/plugin-invert" "^0.14.0" + "@jimp/plugin-mask" "^0.14.0" + "@jimp/plugin-normalize" "^0.14.0" + "@jimp/plugin-print" "^0.14.0" + "@jimp/plugin-resize" "^0.14.0" + "@jimp/plugin-rotate" "^0.14.0" + "@jimp/plugin-scale" "^0.14.0" + "@jimp/plugin-shadow" "^0.14.0" + "@jimp/plugin-threshold" "^0.14.0" timm "^1.6.1" -"@jimp/png@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.9.6.tgz#00ed7e6fb783b94f2f1a9fadf9a42bd75f70cc7f" - integrity sha512-9vhOG2xylcDqPbBf4lzpa2Sa1WNJrEZNGvPvWcM+XVhqYa8+DJBLYkoBlpI/qWIYA+eVWDnLF3ygtGj8CElICw== +"@jimp/png@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.14.0.tgz#0f2dddb5125c0795ca7e67c771204c5437fcda4b" + integrity sha512-0RV/mEIDOrPCcNfXSPmPBqqSZYwGADNRVUTyMt47RuZh7sugbYdv/uvKmQSiqRdR0L1sfbCBMWUEa5G/8MSbdA== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.9.6" - core-js "^3.4.1" + "@jimp/utils" "^0.14.0" pngjs "^3.3.3" -"@jimp/tiff@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.9.6.tgz#9ff12122e727ee15f27f40a710516102a636f66b" - integrity sha512-pKKEMqPzX9ak8mek2iVVoW34+h/TSWUyI4NjbYWJMQ2WExfuvEJvLocy9Q9xi6HqRuJmUxgNIiC5iZM1PDEEfg== +"@jimp/tiff@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.14.0.tgz#a5b25bbe7c43fc3b07bad4e2ab90e0e164c1967f" + integrity sha512-zBYDTlutc7j88G/7FBCn3kmQwWr0rmm1e0FKB4C3uJ5oYfT8645lftUsvosKVUEfkdmOaMAnhrf4ekaHcb5gQw== dependencies: "@babel/runtime" "^7.7.2" - core-js "^3.4.1" utif "^2.0.1" -"@jimp/types@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.9.6.tgz#7be7f415ad93be733387c03b8a228c587a868a18" - integrity sha512-PSjdbLZ8d50En+Wf1XkWFfrXaf/GqyrxxgIwFWPbL+wrW4pmbYovfxSLCY61s8INsOFOft9dzzllhLBtg1aQ6A== +"@jimp/types@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.14.0.tgz#ef681ff702883c5f105b5e4e30d49abf39ee9e34" + integrity sha512-hx3cXAW1KZm+b+XCrY3LXtdWy2U+hNtq0rPyJ7NuXCjU7lZR3vIkpz1DLJ3yDdS70hTi5QDXY3Cd9kd6DtloHQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/bmp" "^0.9.6" - "@jimp/gif" "^0.9.6" - "@jimp/jpeg" "^0.9.6" - "@jimp/png" "^0.9.6" - "@jimp/tiff" "^0.9.6" - core-js "^3.4.1" + "@jimp/bmp" "^0.14.0" + "@jimp/gif" "^0.14.0" + "@jimp/jpeg" "^0.14.0" + "@jimp/png" "^0.14.0" + "@jimp/tiff" "^0.14.0" timm "^1.6.1" -"@jimp/utils@^0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.9.6.tgz#a3e6c29e835e2b9ea9f3899c9d3d230dc63bd518" - integrity sha512-kzxcp0i4ecSdMXFEmtH+NYdBQysINEUTsrjm7v0zH8t/uwaEMOG46I16wo/iPBXJkUeNdL2rbXoGoxxoeSfrrA== +"@jimp/utils@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.14.0.tgz#296254e63118554c62c31c19ac6b8c4bfe6490e5" + integrity sha512-MY5KFYUru0y74IsgM/9asDwb3ERxWxXEu3CRCZEvE7DtT86y1bR1XgtlSliMrptjz4qbivNGMQSvUBpEFJDp1A== dependencies: "@babel/runtime" "^7.7.2" - core-js "^3.4.1" + regenerator-runtime "^0.13.3" "@mapbox/extent@0.4.0": version "0.4.0" @@ -10924,7 +10935,7 @@ core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.1, core-js@^2.5.3, core-js@^2.6.5, resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== -core-js@^3.0.1, core-js@^3.0.4, core-js@^3.4.1, core-js@^3.6.4: +core-js@^3.0.1, core-js@^3.0.4, core-js@^3.6.4: version "3.6.4" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== @@ -15487,6 +15498,14 @@ gh-got@^5.0.0: got "^6.2.0" is-plain-obj "^1.1.0" +gifwrap@^0.9.2: + version "0.9.2" + resolved "https://registry.yarnpkg.com/gifwrap/-/gifwrap-0.9.2.tgz#348e286e67d7cf57942172e1e6f05a71cee78489" + integrity sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA== + dependencies: + image-q "^1.1.1" + omggif "^1.0.10" + git-clone@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/git-clone/-/git-clone-0.1.0.tgz#0d76163778093aef7f1c30238f2a9ef3f07a2eb9" @@ -17368,6 +17387,11 @@ ignore@^5.1.1, ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +image-q@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/image-q/-/image-q-1.1.1.tgz#fc84099664460b90ca862d9300b6bfbbbfbf8056" + integrity sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY= + image-size@^0.8.2: version "0.8.3" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.8.3.tgz#f0b568857e034f29baffd37013587f2c0cad8b46" @@ -19398,16 +19422,15 @@ jest@^25.5.4: import-local "^3.0.2" jest-cli "^25.5.4" -jimp@^0.9.6: - version "0.9.6" - resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.9.6.tgz#abf381daf193a4fa335cb4ee0e22948049251eb9" - integrity sha512-DBDHYeNVqVpoPkcvo0PKTNGvD+i7NYvkKTsl0I3k7ql36uN8wGTptRg0HtgQyYE/bhGSLI6Lq5qLwewaOPXNfg== +jimp@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.14.0.tgz#fde55f69bdb918c1b01ac633d89a25853af85625" + integrity sha512-8BXU+J8+SPmwwyq9ELihpSV4dWPTiOKBWCEgtkbnxxAVMjXdf3yGmyaLSshBfXc8sP/JQ9OZj5R8nZzz2wPXgA== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/custom" "^0.9.6" - "@jimp/plugins" "^0.9.6" - "@jimp/types" "^0.9.6" - core-js "^3.4.1" + "@jimp/custom" "^0.14.0" + "@jimp/plugins" "^0.14.0" + "@jimp/types" "^0.14.0" regenerator-runtime "^0.13.3" jit-grunt@0.10.0: @@ -19429,10 +19452,10 @@ joi@13.x.x, joi@^13.5.2: isemail "3.x.x" topo "3.x.x" -jpeg-js@^0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.3.4.tgz#dc2ba501ee3d58b7bb893c5d1fab47294917e7e7" - integrity sha512-6IzjQxvnlT8UlklNmDXIJMWxijULjqGrzgqc0OG7YadZdvm7KPQ1j0ehmQQHckgEWOfgpptzcnWgESovxudpTA== +jpeg-js@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.1.tgz#937a3ae911eb6427f151760f8123f04c8bfe6ef7" + integrity sha512-jA55yJiB5tCXEddos8JBbvW+IMrqY0y1tjjx9KNVtA+QPmu7ND5j0zkKopClpUTsaETL135uOM2XfcYG4XRjmw== jquery@^3.5.0: version "3.5.0" @@ -23066,6 +23089,11 @@ octokit-pagination-methods@^1.1.0: resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ== +omggif@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" + integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== + omggif@^1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.9.tgz#dcb7024dacd50c52b4d303f04802c91c057c765f" From c8bb0782eae988cf427c47838c867df1ebbd8741 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 28 Jul 2020 11:57:51 -0400 Subject: [PATCH 48/75] [Ingest Manager] Disable unenroll from listing for inactive agent (#73348) --- .../fleet/agent_details_page/components/actions_menu.tsx | 1 + .../ingest_manager/sections/fleet/agent_list_page/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx index 7afc57b30cef4..0f48a230bbf5c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx @@ -53,6 +53,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ onClick={() => { setIsReassignFlyoutOpen(true); }} + disabled={!agent.active} key="reassignConfig" > void; refre onClick={() => { onReassignClick(); }} + disabled={!agent.active} key="reassignConfig" > void; refre defaultMessage="Assign new agent config" /> , - {(unenrollAgentsPrompt) => ( { unenrollAgentsPrompt([agent.id], 1, () => { From 1dbea34d2df3644195391106632acef3c8e7c88c Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Tue, 28 Jul 2020 18:26:48 +0200 Subject: [PATCH 49/75] [Ingest Manager] Don't send kibana version to registry on master. (#73415) * Don't send kibana version to registry on master. * Adjust test. * Create correct app context in mocks. --- x-pack/plugins/ingest_manager/server/mocks.ts | 1 + x-pack/plugins/ingest_manager/server/plugin.ts | 5 +++++ .../ingest_manager/server/services/app_context.ts | 9 +++++++++ .../server/services/epm/registry/index.ts | 14 ++++++++++---- .../apis/epm/list.ts | 2 +- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/mocks.ts b/x-pack/plugins/ingest_manager/server/mocks.ts index f305d9dd0c1a7..52cd294783087 100644 --- a/x-pack/plugins/ingest_manager/server/mocks.ts +++ b/x-pack/plugins/ingest_manager/server/mocks.ts @@ -18,6 +18,7 @@ export const createAppContextStartContractMock = (): IngestManagerAppContext => logger: loggingSystemMock.create().get(), isProductionMode: true, kibanaVersion: '8.0.0', + kibanaBranch: 'master', }; }; diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index 69af475886bb9..5664a87501016 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -15,6 +15,7 @@ import { HttpServiceSetup, } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import packageJSON from '../../../../package.json'; import { LicensingPluginSetup, ILicense } from '../../licensing/server'; import { EncryptedSavedObjectsPluginStart, @@ -85,6 +86,7 @@ export interface IngestManagerAppContext { savedObjects: SavedObjectsServiceStart; isProductionMode: boolean; kibanaVersion: string; + kibanaBranch: string; cloud?: CloudSetup; logger?: Logger; httpSetup?: HttpServiceSetup; @@ -145,6 +147,7 @@ export class IngestManagerPlugin private isProductionMode: boolean; private kibanaVersion: string; + private kibanaBranch: string; private httpSetup: HttpServiceSetup | undefined; private encryptedSavedObjectsSetup: EncryptedSavedObjectsPluginSetup | undefined; @@ -152,6 +155,7 @@ export class IngestManagerPlugin this.config$ = this.initializerContext.config.create(); this.isProductionMode = this.initializerContext.env.mode.prod; this.kibanaVersion = this.initializerContext.env.packageInfo.version; + this.kibanaBranch = packageJSON.branch; this.logger = this.initializerContext.logger.get(); } @@ -257,6 +261,7 @@ export class IngestManagerPlugin savedObjects: core.savedObjects, isProductionMode: this.isProductionMode, kibanaVersion: this.kibanaVersion, + kibanaBranch: this.kibanaBranch, httpSetup: this.httpSetup, cloud: this.cloud, logger: this.logger, diff --git a/x-pack/plugins/ingest_manager/server/services/app_context.ts b/x-pack/plugins/ingest_manager/server/services/app_context.ts index 4d109b73d12d9..bdc7a443ba6dd 100644 --- a/x-pack/plugins/ingest_manager/server/services/app_context.ts +++ b/x-pack/plugins/ingest_manager/server/services/app_context.ts @@ -24,6 +24,7 @@ class AppContextService { private savedObjects: SavedObjectsServiceStart | undefined; private isProductionMode: boolean = false; private kibanaVersion: string | undefined; + private kibanaBranch: string | undefined; private cloud?: CloudSetup; private logger: Logger | undefined; private httpSetup?: HttpServiceSetup; @@ -38,6 +39,7 @@ class AppContextService { this.cloud = appContext.cloud; this.logger = appContext.logger; this.kibanaVersion = appContext.kibanaVersion; + this.kibanaBranch = appContext.kibanaBranch; this.httpSetup = appContext.httpSetup; if (appContext.config$) { @@ -125,6 +127,13 @@ class AppContextService { return this.kibanaVersion; } + public getKibanaBranch() { + if (!this.kibanaBranch) { + throw new Error('Kibana branch is not set.'); + } + return this.kibanaBranch; + } + public addExternalCallback(type: ExternalCallback[0], callback: ExternalCallback[1]) { if (!this.externalCallbacks.has(type)) { this.externalCallbacks.set(type, new Set()); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index 7fb13e5e671d0..c7f2df38fe41a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -40,6 +40,8 @@ export const pkgToPkgKey = ({ name, version }: { name: string; version: string } export async function fetchList(params?: SearchParams): Promise { const registryUrl = getRegistryUrl(); const url = new URL(`${registryUrl}/search`); + const kibanaVersion = appContextService.getKibanaVersion().split('-')[0]; // may be x.y.z-SNAPSHOT + const kibanaBranch = appContextService.getKibanaBranch(); if (params) { if (params.category) { url.searchParams.set('category', params.category); @@ -48,8 +50,9 @@ export async function fetchList(params?: SearchParams): Promise { const registryUrl = getRegistryUrl(); + const kibanaVersion = appContextService.getKibanaVersion().split('-')[0]; // may be x.y.z-SNAPSHOT + const kibanaBranch = appContextService.getKibanaBranch(); const url = new URL( `${registryUrl}/search?package=${packageName}&internal=true&experimental=true` ); - const kibanaVersion = appContextService.getKibanaVersion().split('-')[0]; // may be 8.0.0-SNAPSHOT - if (kibanaVersion) { + + // on master, request all packages regardless of version + if (kibanaVersion && kibanaBranch !== 'master') { url.searchParams.set('kibana.version', kibanaVersion); } const res = await fetchUrl(url.toString()); diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts index 2fbda8f2d3c81..98b26c1c04ebb 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts @@ -29,7 +29,7 @@ export default function ({ getService }: FtrProviderContext) { return response.body; }; const listResponse = await fetchPackageList(); - expect(listResponse.response.length).to.be(6); + expect(listResponse.response.length).to.be(13); } else { warnAndSkipTest(this, log); } From 12e7d995f9b73c71d287341e801fe5c72a05097d Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Tue, 28 Jul 2020 12:46:20 -0400 Subject: [PATCH 50/75] [SIEM] [Detections] Reject on value list + other exception entries in single exception item (#73158) Add validation to reject when value list and other exception type are entries in the same exception item. Also adds tests for this situation on the schema validation --- .../lists/common/schemas/types/entries.mock.ts | 14 +++++++++++++- .../types/non_empty_entries_array.test.ts | 18 +++++++++++++++--- .../schemas/types/non_empty_entries_array.ts | 9 +++++++++ .../exceptions/viewer/helpers.test.tsx | 6 ------ 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/lists/common/schemas/types/entries.mock.ts b/x-pack/plugins/lists/common/schemas/types/entries.mock.ts index 3ed3f4e7ff88f..16794415138b2 100644 --- a/x-pack/plugins/lists/common/schemas/types/entries.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/entries.mock.ts @@ -11,10 +11,22 @@ import { getEntryListMock } from './entry_list.mock'; import { getEntryExistsMock } from './entry_exists.mock'; import { getEntryNestedMock } from './entry_nested.mock'; -export const getEntriesArrayMock = (): EntriesArray => [ +export const getListAndNonListEntriesArrayMock = (): EntriesArray => [ { ...getEntryMatchMock() }, { ...getEntryMatchAnyMock() }, { ...getEntryListMock() }, { ...getEntryExistsMock() }, { ...getEntryNestedMock() }, ]; + +export const getListEntriesArrayMock = (): EntriesArray => [ + { ...getEntryListMock() }, + { ...getEntryListMock() }, +]; + +export const getEntriesArrayMock = (): EntriesArray => [ + { ...getEntryMatchMock() }, + { ...getEntryMatchAnyMock() }, + { ...getEntryExistsMock() }, + { ...getEntryNestedMock() }, +]; diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.test.ts b/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.test.ts index ab7002982cf28..a2697286aa038 100644 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.test.ts @@ -11,10 +11,13 @@ import { foldLeftRight, getPaths } from '../../siem_common_deps'; import { getEntryMatchMock } from './entry_match.mock'; import { getEntryMatchAnyMock } from './entry_match_any.mock'; -import { getEntryListMock } from './entry_list.mock'; import { getEntryExistsMock } from './entry_exists.mock'; import { getEntryNestedMock } from './entry_nested.mock'; -import { getEntriesArrayMock } from './entries.mock'; +import { + getEntriesArrayMock, + getListAndNonListEntriesArrayMock, + getListEntriesArrayMock, +} from './entries.mock'; import { nonEmptyEntriesArray } from './non_empty_entries_array'; import { EntriesArray } from './entries'; @@ -80,7 +83,7 @@ describe('non_empty_entries_array', () => { }); test('it should validate an array of "list" entries', () => { - const payload: EntriesArray = [{ ...getEntryListMock() }, { ...getEntryListMock() }]; + const payload: EntriesArray = [...getListEntriesArrayMock()]; const decoded = nonEmptyEntriesArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -106,6 +109,15 @@ describe('non_empty_entries_array', () => { expect(message.schema).toEqual(payload); }); + test('it should NOT validate an array of entries of value list and non-value list entries', () => { + const payload: EntriesArray = [...getListAndNonListEntriesArrayMock()]; + const decoded = nonEmptyEntriesArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Cannot have entry of type list and other']); + expect(message.schema).toEqual({}); + }); + test('it should NOT validate an array of non entries', () => { const payload = [1]; const decoded = nonEmptyEntriesArray.decode(payload); diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.ts b/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.ts index 1370fe022c258..3683ca97dedb8 100644 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.ts @@ -8,6 +8,7 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; import { EntriesArray, entriesArray } from './entries'; +import { entriesList } from './entry_list'; /** * Types the nonEmptyEntriesArray as: @@ -21,6 +22,14 @@ export const nonEmptyEntriesArray = new t.Type entriesList.is(entry)) && + input.some((entry) => !entriesList.is(entry)) + ) { + // fail when an exception item contains both a value list entry and a non-value list entry + return t.failure(input, context, 'Cannot have entry of type list and other'); + } return entriesArray.validate(input, context); } }, diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.test.tsx index fe00e3530fa83..5d4340db9a448 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.test.tsx @@ -93,12 +93,6 @@ describe('Exception viewer helpers', () => { operator: 'is one of', value: ['some host name'], }, - { - fieldName: 'host.name', - isNested: false, - operator: 'is in list', - value: 'some-list-id', - }, { fieldName: 'host.name', isNested: false, From 79c475a2158e208e0a6f6c3979c2eaa28e38cc03 Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Tue, 28 Jul 2020 12:59:41 -0400 Subject: [PATCH 51/75] Fixes incorrect platform service usage (#73453) --- x-pack/plugins/canvas/public/state/reducers/workpad.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/x-pack/plugins/canvas/public/state/reducers/workpad.js b/x-pack/plugins/canvas/public/state/reducers/workpad.js index fffcb69c451ed..1c210577e1014 100644 --- a/x-pack/plugins/canvas/public/state/reducers/workpad.js +++ b/x-pack/plugins/canvas/public/state/reducers/workpad.js @@ -40,11 +40,7 @@ export const workpadReducer = handleActions( [setName]: (workpadState, { payload }) => { platformService .getService() - .coreStart.chrome.recentlyAccessed.add( - `${APP_ROUTE_WORKPAD}/${workpadState.id}`, - payload, - workpadState.id - ); + .setRecentlyAccessed(`${APP_ROUTE_WORKPAD}/${workpadState.id}`, payload, workpadState.id); return { ...workpadState, name: payload }; }, From 01b70dd9dce9f576a5524426d864a8b9f94a7d4c Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Tue, 28 Jul 2020 12:04:42 -0500 Subject: [PATCH 52/75] [Security Solution] Fix Lists route permissions (#73368) * Do not display threshold field for an ML Rule * Give 'read' privileges to 'all' users We have several lists routes that require lists-read access. If the user was given the 'all' privilege for securitySolution, they would previously be locked out of those routes. --- .../pages/detection_engine/rules/create/helpers.ts | 7 ++++--- x-pack/plugins/security_solution/server/plugin.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index a972afbd8c0c5..705013beb750f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -12,6 +12,7 @@ import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../../common/const import { transformAlertToRuleAction } from '../../../../../../common/detection_engine/transform_actions'; import { RuleType } from '../../../../../../common/detection_engine/types'; import { isMlRule } from '../../../../../../common/machine_learning/helpers'; +import { isThresholdRule } from '../../../../../../common/detection_engine/utils'; import { List } from '../../../../../../common/detection_engine/schemas/types'; import { ENDPOINT_LIST_ID } from '../../../../../shared_imports'; import { NewRule, Rule } from '../../../../containers/detection_engine/rules'; @@ -57,7 +58,7 @@ export interface RuleFields { } type QueryRuleFields = Omit; type ThresholdRuleFields = Omit; -type MlRuleFields = Omit; +type MlRuleFields = Omit; const isMlFields = ( fields: QueryRuleFields | MlRuleFields | ThresholdRuleFields @@ -69,9 +70,9 @@ const isThresholdFields = ( export const filterRuleFieldsForType = (fields: T, type: RuleType) => { if (isMlRule(type)) { - const { index, queryBar, ...mlRuleFields } = fields; + const { index, queryBar, threshold, ...mlRuleFields } = fields; return mlRuleFields; - } else if (type === 'threshold') { + } else if (isThresholdRule(type)) { const { anomalyThreshold, machineLearningJobId, ...thresholdRuleFields } = fields; return thresholdRuleFields; } else { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 8fc413236dd2c..f2fad16d80414 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -182,7 +182,7 @@ export class Plugin implements IPlugin Date: Tue, 28 Jul 2020 13:22:44 -0400 Subject: [PATCH 53/75] Fix adding new visualization from dashboard when session storage is enabled. --- .../application/dashboard_app_controller.tsx | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 2a0e2889575f3..afccf8deaa217 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -172,6 +172,10 @@ export class DashboardAppController { chrome.docTitle.change(dash.title); } + const incomingEmbeddable = embeddable + .getStateTransfer(scopedHistory()) + .getIncomingEmbeddablePackage(); + const dashboardStateManager = new DashboardStateManager({ savedDashboard: dash, hideWriteControls: dashboardConfig.getHideWriteControls(), @@ -444,21 +448,24 @@ export class DashboardAppController { refreshDashboardContainer(); }); - const incomingState = embeddable - .getStateTransfer(scopedHistory()) - .getIncomingEmbeddablePackage(); - if (incomingState) { - if ('id' in incomingState) { - container.addOrUpdateEmbeddable(incomingState.type, { - savedObjectId: incomingState.id, - }); - } else if ('input' in incomingState) { - const input = incomingState.input; + if (incomingEmbeddable) { + if ('id' in incomingEmbeddable) { + container.addOrUpdateEmbeddable( + incomingEmbeddable.type, + { + savedObjectId: incomingEmbeddable.id, + } + ); + } else if ('input' in incomingEmbeddable) { + const input = incomingEmbeddable.input; delete input.id; const explicitInput = { savedVis: input, }; - container.addOrUpdateEmbeddable(incomingState.type, explicitInput); + container.addOrUpdateEmbeddable( + incomingEmbeddable.type, + explicitInput + ); } } } From 2ea2f10c4652a848d2ff3c14cd1d327dc32ca1ee Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 28 Jul 2020 18:26:59 +0100 Subject: [PATCH 54/75] [Security Solution] Super select (#73271) * fix icon * fix items * Cleanup * match styling Co-authored-by: Elastic Machine Co-authored-by: Patryk Kopycinski --- .../timeline/search_super_select/index.tsx | 63 ++++++++++--------- .../timeline/selectable_timeline/index.tsx | 2 +- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx index 825d4fe3b29b1..3019a8add4e36 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx @@ -4,19 +4,41 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiInputPopover, EuiSelectableOption, EuiSuperSelect } from '@elastic/eui'; +import { EuiInputPopover, EuiSelectableOption, EuiFieldText } from '@elastic/eui'; import React, { memo, useCallback, useMemo, useState } from 'react'; -import { createGlobalStyle } from 'styled-components'; +import styled from 'styled-components'; import { OpenTimelineResult } from '../../open_timeline/types'; import { SelectableTimeline } from '../selectable_timeline'; import * as i18n from '../translations'; import { TimelineType, TimelineTypeLiteral } from '../../../../../common/types/timeline'; -const SearchTimelineSuperSelectGlobalStyle = createGlobalStyle` - .euiPopover__panel.euiPopover__panel-isOpen.timeline-search-super-select-popover__popoverPanel { - visibility: hidden; - z-index: 0; +const StyledEuiFieldText = styled(EuiFieldText)` + padding-left: 12px; + padding-right: 40px; + + &[readonly] { + cursor: pointer; + background-size: 0 100%; + background-repeat: no-repeat; + + // To match EuiFieldText focus state + &:focus { + background-color: #fff; + background-image: linear-gradient( + to top, + #006bb4, + #006bb4 2px, + transparent 2px, + transparent 100% + ); + background-size: 100% 100%; + } + } + + & + .euiFormControlLayoutIcons { + left: unset; + right: 12px; } `; @@ -29,13 +51,6 @@ interface SearchTimelineSuperSelectProps { onTimelineChange: (timelineTitle: string, timelineId: string | null) => void; } -const basicSuperSelectOptions = [ - { - value: '-1', - inputDisplay: i18n.DEFAULT_TIMELINE_TITLE, - }, -]; - const getBasicSelectableOptions = (timelineId: string) => [ { description: i18n.DEFAULT_TIMELINE_DESCRIPTION, @@ -67,26 +82,15 @@ const SearchTimelineSuperSelectComponent: React.FC ( - ), - [handleOpenPopover, isDisabled, timelineId, timelineTitle] + [handleOpenPopover, isDisabled, timelineTitle] ); const handleGetSelectableOptions = useCallback( @@ -126,7 +130,6 @@ const SearchTimelineSuperSelectComponent: React.FC - ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx index ae8bf53090789..7ecbc9a53cb21 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx @@ -165,7 +165,7 @@ const SelectableTimelineComponent: React.FC = ({ responsive={false} > - + From f61df057727c4adf0aa4bd1e72f108ec23945567 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Tue, 28 Jul 2020 10:57:38 -0700 Subject: [PATCH 55/75] Fix long combo box items breaking out of flex item width (#73351) --- .../components/package_config_input_config.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/components/package_config_input_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/components/package_config_input_config.tsx index 98f04dbd92659..fd3a64bc760a0 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/components/package_config_input_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/components/package_config_input_config.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { useState, Fragment, memo, useMemo } from 'react'; +import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGrid, @@ -21,6 +22,10 @@ import { } from '../services'; import { PackageConfigInputVarField } from './package_config_input_var_field'; +const FlexItemWithMaxWidth = styled(EuiFlexItem)` + max-width: calc(50% - ${(props) => props.theme.eui.euiSizeL}); +`; + export const PackageConfigInputConfig: React.FunctionComponent<{ packageInputVars?: RegistryVarsEntry[]; packageConfigInput: PackageConfigInput; @@ -88,7 +93,7 @@ export const PackageConfigInputConfig: React.FunctionComponent<{ - + {requiredVars.map((varDef) => { const { name: varName, type: varType } = varDef; @@ -176,7 +181,7 @@ export const PackageConfigInputConfig: React.FunctionComponent<{ ) : null} - + ); } From 86b60bbc637471ca5211b31042b7dfd89bbd2f5e Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 28 Jul 2020 14:52:12 -0400 Subject: [PATCH 56/75] Mock prototype in unit test to prevent relative date breaking snapshots. (#73531) --- .../common/charts/__tests__/ping_histogram.test.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/ping_histogram.test.tsx b/x-pack/plugins/uptime/public/components/common/charts/__tests__/ping_histogram.test.tsx index 57a38f2a949e7..73c6ee43ccd07 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/ping_histogram.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/ping_histogram.test.tsx @@ -7,8 +7,13 @@ import React from 'react'; import { PingHistogramComponent, PingHistogramComponentProps } from '../ping_histogram'; import { renderWithRouter, shallowWithRouter, MountWithReduxProvider } from '../../../../lib'; +import moment from 'moment'; describe('PingHistogram component', () => { + beforeAll(() => { + moment.prototype.fromNow = jest.fn(() => 'a year ago'); + }); + const props: PingHistogramComponentProps = { absoluteStartDate: 1548697920000, absoluteEndDate: 1548700920000, From 2d8a41d3679ff77a60c6c417cdc10f88a4ed3203 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Tue, 28 Jul 2020 12:39:36 -0700 Subject: [PATCH 57/75] [Metrics UI] Make composite size configurable to avoid max buckets (#72955) Co-authored-by: Elastic Machine --- x-pack/plugins/infra/common/http_api/snapshot_api.ts | 1 + x-pack/plugins/infra/public/metrics_overview_fetchers.test.ts | 1 + x-pack/plugins/infra/public/metrics_overview_fetchers.ts | 1 + x-pack/plugins/infra/server/lib/snapshot/snapshot.ts | 4 ++-- x-pack/plugins/infra/server/routes/snapshot/index.ts | 2 ++ 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/common/http_api/snapshot_api.ts b/x-pack/plugins/infra/common/http_api/snapshot_api.ts index 9ddbcb17089f3..11cb57238f917 100644 --- a/x-pack/plugins/infra/common/http_api/snapshot_api.ts +++ b/x-pack/plugins/infra/common/http_api/snapshot_api.ts @@ -107,6 +107,7 @@ export const SnapshotRequestRT = rt.intersection([ region: rt.string, filterQuery: rt.union([rt.string, rt.null]), includeTimeseries: rt.boolean, + overrideCompositeSize: rt.number, }), ]); diff --git a/x-pack/plugins/infra/public/metrics_overview_fetchers.test.ts b/x-pack/plugins/infra/public/metrics_overview_fetchers.test.ts index 88bc426e9a0f7..135b4ea9a5335 100644 --- a/x-pack/plugins/infra/public/metrics_overview_fetchers.test.ts +++ b/x-pack/plugins/infra/public/metrics_overview_fetchers.test.ts @@ -75,6 +75,7 @@ describe('Metrics UI Observability Homepage Functions', () => { groupBy: [], nodeType: 'host', includeTimeseries: true, + overrideCompositeSize: 5, timerange: { from: startTime.valueOf(), to: endTime.valueOf(), diff --git a/x-pack/plugins/infra/public/metrics_overview_fetchers.ts b/x-pack/plugins/infra/public/metrics_overview_fetchers.ts index 4eaf903e17608..79e0850635138 100644 --- a/x-pack/plugins/infra/public/metrics_overview_fetchers.ts +++ b/x-pack/plugins/infra/public/metrics_overview_fetchers.ts @@ -87,6 +87,7 @@ export const createMetricsFetchData = ( groupBy: [], nodeType: 'host', includeTimeseries: true, + overrideCompositeSize: 5, timerange: { from: start, to: end, diff --git a/x-pack/plugins/infra/server/lib/snapshot/snapshot.ts b/x-pack/plugins/infra/server/lib/snapshot/snapshot.ts index 9ca10d5e39da7..5f359b0523d9f 100644 --- a/x-pack/plugins/infra/server/lib/snapshot/snapshot.ts +++ b/x-pack/plugins/infra/server/lib/snapshot/snapshot.ts @@ -86,7 +86,7 @@ const requestGroupedNodes = async ( aggregations: { nodes: { composite: { - size: SNAPSHOT_COMPOSITE_REQUEST_SIZE, + size: options.overrideCompositeSize || SNAPSHOT_COMPOSITE_REQUEST_SIZE, sources: getGroupedNodesSources(options), }, aggs: { @@ -142,7 +142,7 @@ const requestNodeMetrics = async ( aggregations: { nodes: { composite: { - size: SNAPSHOT_COMPOSITE_REQUEST_SIZE, + size: options.overrideCompositeSize || SNAPSHOT_COMPOSITE_REQUEST_SIZE, sources: getMetricsSources(options), }, aggregations: { diff --git a/x-pack/plugins/infra/server/routes/snapshot/index.ts b/x-pack/plugins/infra/server/routes/snapshot/index.ts index 7c81c6eef675f..e99103080463e 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/index.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/index.ts @@ -40,6 +40,7 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => { accountId, region, includeTimeseries, + overrideCompositeSize, } = pipe( SnapshotRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) @@ -59,6 +60,7 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => { metrics, timerange, includeTimeseries, + overrideCompositeSize, }; const searchES = ( From 5e624502f82085802333c225882e0d91665355e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Tue, 28 Jul 2020 22:13:02 +0200 Subject: [PATCH 58/75] [Security Solution] Fix query fetchPolicy and deduplication (#73199) --- .../components/events_viewer/events_viewer.tsx | 1 + .../common/components/events_viewer/index.tsx | 5 ++++- .../common/components/events_viewer/mock.ts | 1 + .../public/common/containers/source/index.tsx | 17 +++++++++++++---- .../components/alerts_table/index.tsx | 7 ++++--- .../components/rules/step_about_rule/index.tsx | 3 ++- .../components/rules/step_define_rule/index.tsx | 2 +- .../rules/fetch_index_patterns.tsx | 2 +- .../timelines/components/timeline/timeline.tsx | 1 + .../public/timelines/containers/index.tsx | 9 ++++++++- 10 files changed, 36 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index bc036b38524ba..e836e2e20432a 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -222,6 +222,7 @@ const EventsViewerComponent: React.FC = ({ sourceId="default" startDate={start} endDate={end} + queryDeduplication="events_viewer" > {({ events, diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 80831b4022ace..c402116ee2714 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -69,7 +69,10 @@ const StatefulEventsViewerComponent: React.FC = ({ }) => { const [ { docValueFields, browserFields, indexPatterns, isLoading: isLoadingIndexPattern }, - ] = useFetchIndexPatterns(defaultIndices ?? useUiSetting(DEFAULT_INDEX_KEY)); + ] = useFetchIndexPatterns( + defaultIndices ?? useUiSetting(DEFAULT_INDEX_KEY), + 'events_viewer' + ); useEffect(() => { if (createTimeline != null) { diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/mock.ts b/x-pack/plugins/security_solution/public/common/components/events_viewer/mock.ts index ea2e60ebc82b8..6266e84051901 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/mock.ts +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/mock.ts @@ -40,6 +40,7 @@ export const mockEventViewerResponse = [ { field: 'event.end', format: 'date_time' }, ], inspect: false, + queryDeduplication: 'events_viewer', }, }, result: { diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index 54d49d7279d68..ffbecf9e3d433 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -122,7 +122,12 @@ interface UseWithSourceState { export const useWithSource = ( sourceId = 'default', indexToAdd?: string[] | null, - onlyCheckIndexToAdd?: boolean + onlyCheckIndexToAdd?: boolean, + // Fun fact: When using this hook multiple times within a component (e.g. add_exception_modal & edit_exception_modal), + // the apolloClient will perform queryDeduplication and prevent the first query from executing. A deep compare is not + // performed on `indices`, so another field must be passed to circumvent this. + // For details, see https://github.com/apollographql/react-apollo/issues/2202 + queryDeduplication = 'default' ) => { const [configIndex] = useUiSetting$(DEFAULT_INDEX_KEY); const defaultIndex = useMemo(() => { @@ -154,12 +159,16 @@ export const useWithSource = ( setState((prevState) => ({ ...prevState, loading: true })); try { - const result = await apolloClient.query({ + const result = await apolloClient.query< + SourceQuery.Query, + SourceQuery.Variables & { queryDeduplication: string } + >({ query: sourceQuery, - fetchPolicy: 'network-only', + fetchPolicy: 'cache-first', variables: { sourceId, defaultIndex, + queryDeduplication, }, context: { fetchOptions: { @@ -206,7 +215,7 @@ export const useWithSource = ( isSubscribed = false; return abortCtrl.abort(); }; - }, [apolloClient, sourceId, defaultIndex]); + }, [apolloClient, sourceId, defaultIndex, queryDeduplication]); return state; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 1eda358fe5944..ab95e433d92f3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -116,8 +116,9 @@ export const AlertsTableComponent: React.FC = ({ const [addExceptionModalState, setAddExceptionModalState] = useState( addExceptionModalInitialState ); - const [{ browserFields, indexPatterns }] = useFetchIndexPatterns( - signalsIndex !== '' ? [signalsIndex] : [] + const [{ browserFields, indexPatterns, isLoading: indexPatternsLoading }] = useFetchIndexPatterns( + signalsIndex !== '' ? [signalsIndex] : [], + 'alerts_table' ); const kibana = useKibana(); const [, dispatchToaster] = useStateToaster(); @@ -433,7 +434,7 @@ export const AlertsTableComponent: React.FC = ({ closeAddExceptionModal, ]); - if (loading || isEmpty(signalsIndex)) { + if (loading || indexPatternsLoading || isEmpty(signalsIndex)) { return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx index ec812fa63eadf..5edf6f0a9312e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx @@ -74,7 +74,8 @@ const StepAboutRuleComponent: FC = ({ const initialState = defaultValues ?? stepAboutDefaultValue; const [myStepData, setMyStepData] = useState(initialState); const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns( - defineRuleData?.index ?? [] + defineRuleData?.index ?? [], + 'step_about_rule' ); const canUseExceptions = defineRuleData?.ruleType && diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 51e9291f31941..3d5b66b8869cc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -117,7 +117,7 @@ const StepDefineRuleComponent: FC = ({ const [myStepData, setMyStepData] = useState(initialState); const [ { browserFields, indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, - ] = useFetchIndexPatterns(myStepData.index); + ] = useFetchIndexPatterns(myStepData.index, 'step_define_rule'); const { form } = useForm({ defaultValue: initialState, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx index c0997a5e62908..82c9292af7451 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx @@ -77,7 +77,7 @@ export const useFetchIndexPatterns = ( apolloClient .query({ query: sourceQuery, - fetchPolicy: 'network-only', + fetchPolicy: 'cache-first', variables: { sourceId: 'default', defaultIndex: indices, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx index c27af94addeab..a2ee1e56306b5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx @@ -286,6 +286,7 @@ export const TimelineComponent: React.FC = ({ filterQuery={combinedQueries!.filterQuery} sortField={timelineQuerySortField} startDate={start} + queryDeduplication="timeline" > {({ events, diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 510d58dbe6a69..562999108b4b0 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -58,6 +58,7 @@ export interface OwnProps extends QueryTemplateProps { sortField: SortField; fields: string[]; startDate: string; + queryDeduplication: string; } type TimelineQueryProps = OwnProps & PropsFromRedux & WithKibanaProps & CustomReduxProps; @@ -93,6 +94,7 @@ class TimelineQueryComponent extends QueryTemplate< sourceId, sortField, startDate, + queryDeduplication, } = this.props; const defaultKibanaIndex = kibana.services.uiSettings.get(DEFAULT_INDEX_KEY); const defaultIndex = @@ -102,7 +104,11 @@ class TimelineQueryComponent extends QueryTemplate< ...(['all', 'alert', 'signal'].includes(eventType) ? indexToAdd : []), ] : indexPattern?.title.split(',') ?? []; - const variables: GetTimelineQuery.Variables = { + // Fun fact: When using this hook multiple times within a component (e.g. add_exception_modal & edit_exception_modal), + // the apolloClient will perform queryDeduplication and prevent the first query from executing. A deep compare is not + // performed on `indices`, so another field must be passed to circumvent this. + // For details, see https://github.com/apollographql/react-apollo/issues/2202 + const variables: GetTimelineQuery.Variables & { queryDeduplication: string } = { fieldRequested: fields, filterQuery: createFilter(filterQuery), sourceId, @@ -116,6 +122,7 @@ class TimelineQueryComponent extends QueryTemplate< defaultIndex, docValueFields: docValueFields ?? [], inspect: isInspected, + queryDeduplication, }; return ( From 0b3dab7318c49e95a53c5f207bd1a850f0c2ef8a Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 28 Jul 2020 14:25:32 -0600 Subject: [PATCH 59/75] [Security Solution][Detections] Fixes Risk Score and Severity mapping issues (#73233) ## Summary Fixes the following issues around Risk Score/Severity mapping: * Severity override option cannot be unselected during rule creation * Risk score override option cannot be unselected during rule creation * Cannot fill Critical Severity override at the first attempt * Cannot create a rule with just a Critical severity override Note: When editing rules there is the possibility of the mapping fields remaining `disabled` as they are locked to the 'isLoading' flag from the gql `useFetchIndexPatterns` call, which can sometimes not return/get stuck as loading. @patrykkopycinski has a draft PR to fix this here: https://github.com/elastic/kibana/pull/73199 cc @MadameSheema ##### Severity Mapping Fixes:

      Now distinguishes between empty string/value

      ##### Risk Score Mapping Fixes:

      ### 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/master/packages/kbn-i18n/README.md) - [X] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials * Working with @benskelker on API docs. This PR adds `risk_score` (can be `undefined`) to `risk_score.mapping` for future compatibility with mapping to specific risk score values. - [X] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios --- .../schemas/common/schemas.ts | 3 +- .../rules/description_step/helpers.test.tsx | 1 + .../rules/description_step/helpers.tsx | 114 ++++---- .../rules/risk_score_mapping/index.tsx | 66 ++--- .../rules/severity_mapping/index.tsx | 248 ++++++++++-------- .../rules/step_about_rule/default_value.ts | 5 +- .../rules/step_about_rule/index.test.tsx | 9 +- .../rules/step_about_rule/schema.tsx | 10 +- .../rules/all/__mocks__/mock.ts | 5 +- .../detection_engine/rules/create/helpers.ts | 8 +- .../detection_engine/rules/helpers.test.tsx | 5 +- .../pages/detection_engine/rules/helpers.tsx | 24 +- .../pages/detection_engine/rules/types.ts | 2 + .../mappings/build_risk_score_from_mapping.ts | 9 +- 14 files changed, 294 insertions(+), 215 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 273ea72a2ffe3..21fa780badd84 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -222,8 +222,9 @@ export const risk_score_mapping_value = t.string; export const risk_score_mapping_item = t.exact( t.type({ field: risk_score_mapping_field, - operator, value: risk_score_mapping_value, + operator, + risk_score: riskScoreOrUndefined, }) ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx index 2a6cd3fc5bb7a..0d98a0f2f26ff 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx @@ -331,6 +331,7 @@ describe('helpers', () => { const result: ListItems[] = buildSeverityDescription({ value: 'low', mapping: [{ field: 'host.name', operator: 'equals', value: 'hello', severity: 'high' }], + isMappingChecked: true, }); expect(result[0].title).toEqual('Severity'); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx index 1110c8c098988..600bc999849d1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx @@ -35,6 +35,7 @@ import { SeverityBadge } from '../severity_badge'; import ListTreeIcon from './assets/list_tree_icon.svg'; import { assertUnreachable } from '../../../../common/lib/helpers'; import { AboutStepRiskScore, AboutStepSeverity } from '../../../pages/detection_engine/rules/types'; +import { defaultToEmptyTag } from '../../../../common/components/empty_value'; const NoteDescriptionContainer = styled(EuiFlexItem)` height: 105px; @@ -236,35 +237,44 @@ export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems title: i18nSeverity.DEFAULT_SEVERITY, description: , }, - ...severity.mapping.map((severityItem, index) => { - return { - title: index === 0 ? i18nSeverity.SEVERITY_MAPPING : '', - description: ( - - - - <>{severityItem.field} - - - - <>{severityItem.value} - - - - - - - - - ), - }; - }), + ...(severity.isMappingChecked + ? severity.mapping + .filter((severityItem) => severityItem.field !== '') + .map((severityItem, index) => { + return { + title: index === 0 ? i18nSeverity.SEVERITY_MAPPING : '', + description: ( + + + + <>{`${severityItem.field}:`} + + + + + {defaultToEmptyTag(severityItem.value)} + + + + + + + + + + ), + }; + }) + : []), ]; export const buildRiskScoreDescription = (riskScore: AboutStepRiskScore): ListItems[] => [ @@ -272,27 +282,31 @@ export const buildRiskScoreDescription = (riskScore: AboutStepRiskScore): ListIt title: i18nRiskScore.RISK_SCORE, description: riskScore.value, }, - ...riskScore.mapping.map((riskScoreItem, index) => { - return { - title: index === 0 ? i18nRiskScore.RISK_SCORE_MAPPING : '', - description: ( - - - - <>{riskScoreItem.field} - - - - - - {'signal.rule.risk_score'} - - ), - }; - }), + ...(riskScore.isMappingChecked + ? riskScore.mapping + .filter((riskScoreItem) => riskScoreItem.field !== '') + .map((riskScoreItem, index) => { + return { + title: index === 0 ? i18nRiskScore.RISK_SCORE_MAPPING : '', + description: ( + + + + <>{riskScoreItem.field} + + + + + + {'signal.rule.risk_score'} + + ), + }; + }) + : []), ]; const MyRefUrlLink = styled(EuiLink)` diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx index c9e2cb1a8ca24..35816e82540d1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx @@ -14,8 +14,9 @@ import { EuiIcon, EuiSpacer, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; +import { noop } from 'lodash/fp'; import * as i18n from './translations'; import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; import { CommonUseField } from '../../../../cases/components/create'; @@ -24,6 +25,10 @@ import { FieldComponent } from '../../../../common/components/autocomplete/field import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +const RiskScoreMappingEuiFormRow = styled(EuiFormRow)` + width: 468px; +`; + const NestedContent = styled.div` margin-left: 24px; `; @@ -41,6 +46,7 @@ interface RiskScoreFieldProps { field: FieldHook; idAria: string; indices: IIndexPattern; + isDisabled: boolean; placeholder?: string; } @@ -49,40 +55,23 @@ export const RiskScoreField = ({ field, idAria, indices, + isDisabled, placeholder, }: RiskScoreFieldProps) => { - const [isRiskScoreMappingChecked, setIsRiskScoreMappingChecked] = useState(false); - const [initialFieldCheck, setInitialFieldCheck] = useState(true); - const fieldTypeFilter = useMemo(() => ['number'], []); - useEffect(() => { - if ( - !isRiskScoreMappingChecked && - initialFieldCheck && - (field.value as AboutStepRiskScore).mapping?.length > 0 - ) { - setIsRiskScoreMappingChecked(true); - setInitialFieldCheck(false); - } - }, [ - field, - initialFieldCheck, - isRiskScoreMappingChecked, - setIsRiskScoreMappingChecked, - setInitialFieldCheck, - ]); - const handleFieldChange = useCallback( ([newField]: IFieldType[]): void => { const values = field.value as AboutStepRiskScore; field.setValue({ value: values.value, + isMappingChecked: values.isMappingChecked, mapping: [ { field: newField?.name ?? '', operator: 'equals', - value: '', + value: undefined, + riskScore: undefined, }, ], }); @@ -99,8 +88,13 @@ export const RiskScoreField = ({ }, [field.value, indices]); const handleRiskScoreMappingChecked = useCallback(() => { - setIsRiskScoreMappingChecked(!isRiskScoreMappingChecked); - }, [isRiskScoreMappingChecked, setIsRiskScoreMappingChecked]); + const values = field.value as AboutStepRiskScore; + field.setValue({ + value: values.value, + mapping: [...values.mapping], + isMappingChecked: !values.isMappingChecked, + }); + }, [field]); const riskScoreLabel = useMemo(() => { return ( @@ -117,11 +111,16 @@ export const RiskScoreField = ({ const riskScoreMappingLabel = useMemo(() => { return (
      - + @@ -133,7 +132,7 @@ export const RiskScoreField = ({
      ); - }, [handleRiskScoreMappingChecked, isRiskScoreMappingChecked]); + }, [field.value, handleRiskScoreMappingChecked, isDisabled]); return ( @@ -153,6 +152,7 @@ export const RiskScoreField = ({ componentProps={{ idAria: 'detectionEngineStepAboutRuleRiskScore', 'data-test-subj': 'detectionEngineStepAboutRuleRiskScore', + isDisabled, euiFieldProps: { max: 100, min: 0, @@ -166,11 +166,11 @@ export const RiskScoreField = ({ - {i18n.RISK_SCORE_MAPPING_DETAILS} ) : ( '' @@ -184,7 +184,7 @@ export const RiskScoreField = ({ > - {isRiskScoreMappingChecked && ( + {(field.value as AboutStepRiskScore).isMappingChecked && ( @@ -208,11 +208,11 @@ export const RiskScoreField = ({ fieldTypeFilter={fieldTypeFilter} isLoading={false} isClearable={false} - isDisabled={false} + isDisabled={isDisabled} onChange={handleFieldChange} data-test-subj={dataTestSubj} aria-label={idAria} - fieldInputWidth={230} + fieldInputWidth={270} /> @@ -226,7 +226,7 @@ export const RiskScoreField = ({ )} - + ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx index 579c60579b32e..54d505a4d867f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx @@ -14,7 +14,8 @@ import { EuiIcon, EuiSpacer, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { noop } from 'lodash/fp'; +import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import * as i18n from './translations'; import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; @@ -27,10 +28,16 @@ import { } from '../../../../../../../../src/plugins/data/common/index_patterns'; import { FieldComponent } from '../../../../common/components/autocomplete/field'; import { AutocompleteFieldMatchComponent } from '../../../../common/components/autocomplete/field_value_match'; +import { + Severity, + SeverityMapping, + SeverityMappingItem, +} from '../../../../../common/detection_engine/schemas/common/schemas'; -const SeverityMappingParentContainer = styled(EuiFlexItem)` - max-width: 471px; +const SeverityMappingEuiFormRow = styled(EuiFormRow)` + width: 468px; `; + const NestedContent = styled.div` margin-left: 24px; `; @@ -48,6 +55,7 @@ interface SeverityFieldProps { field: FieldHook; idAria: string; indices: IIndexPattern; + isDisabled: boolean; options: SeverityOptionItem[]; } @@ -56,42 +64,20 @@ export const SeverityField = ({ field, idAria, indices, + isDisabled, options, }: SeverityFieldProps) => { - const [isSeverityMappingChecked, setIsSeverityMappingChecked] = useState(false); - const [initialFieldCheck, setInitialFieldCheck] = useState(true); const fieldValueInputWidth = 160; - useEffect(() => { - if ( - !isSeverityMappingChecked && - initialFieldCheck && - (field.value as AboutStepSeverity).mapping?.length > 0 - ) { - setIsSeverityMappingChecked(true); - setInitialFieldCheck(false); - } - }, [ - field, - initialFieldCheck, - isSeverityMappingChecked, - setIsSeverityMappingChecked, - setInitialFieldCheck, - ]); - - const handleFieldChange = useCallback( - (index: number, severity: string, [newField]: IFieldType[]): void => { + const handleFieldValueChange = useCallback( + (newMappingItems: SeverityMapping, index: number): void => { const values = field.value as AboutStepSeverity; field.setValue({ value: values.value, + isMappingChecked: values.isMappingChecked, mapping: [ ...values.mapping.slice(0, index), - { - ...values.mapping[index], - field: newField?.name ?? '', - operator: 'equals', - severity, - }, + ...newMappingItems, ...values.mapping.slice(index + 1), ], }); @@ -99,40 +85,59 @@ export const SeverityField = ({ [field] ); + const handleFieldChange = useCallback( + (index: number, severity: Severity, [newField]: IFieldType[]): void => { + const values = field.value as AboutStepSeverity; + const newMappingItems: SeverityMapping = [ + { + ...values.mapping[index], + field: newField?.name ?? '', + value: newField != null ? values.mapping[index].value : '', + operator: 'equals', + severity, + }, + ]; + handleFieldValueChange(newMappingItems, index); + }, + [field, handleFieldValueChange] + ); + const handleFieldMatchValueChange = useCallback( - (index: number, severity: string, newMatchValue: string): void => { + (index: number, severity: Severity, newMatchValue: string): void => { const values = field.value as AboutStepSeverity; - field.setValue({ - value: values.value, - mapping: [ - ...values.mapping.slice(0, index), - { - ...values.mapping[index], - value: newMatchValue, - operator: 'equals', - severity, - }, - ...values.mapping.slice(index + 1), - ], - }); + const newMappingItems: SeverityMapping = [ + { + ...values.mapping[index], + field: values.mapping[index].field, + value: + values.mapping[index].field != null && values.mapping[index].field !== '' + ? newMatchValue + : '', + operator: 'equals', + severity, + }, + ]; + handleFieldValueChange(newMappingItems, index); }, - [field] + [field, handleFieldValueChange] ); - const selectedState = useMemo(() => { - return ( - (field.value as AboutStepSeverity).mapping?.map((mapping) => { - const [newSelectedField] = indices.fields.filter( - ({ name }) => mapping.field != null && mapping.field === name - ); - return { field: newSelectedField, value: mapping.value }; - }) ?? [] - ); - }, [field.value, indices]); + const getIFieldTypeFromFieldName = ( + fieldName: string | undefined, + iIndexPattern: IIndexPattern + ): IFieldType | undefined => { + const [iFieldType] = iIndexPattern.fields.filter(({ name }) => fieldName === name); + return iFieldType; + }; - const handleSeverityMappingSelected = useCallback(() => { - setIsSeverityMappingChecked(!isSeverityMappingChecked); - }, [isSeverityMappingChecked, setIsSeverityMappingChecked]); + const handleSeverityMappingChecked = useCallback(() => { + const values = field.value as AboutStepSeverity; + field.setValue({ + value: values.value, + mapping: [...values.mapping], + isMappingChecked: !values.isMappingChecked, + }); + }, [field]); const severityLabel = useMemo(() => { return ( @@ -149,12 +154,17 @@ export const SeverityField = ({ const severityMappingLabel = useMemo(() => { return (
      - + {i18n.SEVERITY_MAPPING} @@ -165,7 +175,7 @@ export const SeverityField = ({
      ); - }, [handleSeverityMappingSelected, isSeverityMappingChecked]); + }, [field.value, handleSeverityMappingChecked, isDisabled]); return ( @@ -185,6 +195,7 @@ export const SeverityField = ({ componentProps={{ idAria: 'detectionEngineStepAboutRuleSeverity', 'data-test-subj': 'detectionEngineStepAboutRuleSeverity', + isDisabled, euiFieldProps: { fullWidth: false, disabled: false, @@ -195,12 +206,12 @@ export const SeverityField = ({ - - + {i18n.SEVERITY_MAPPING_DETAILS} ) : ( '' @@ -214,7 +225,7 @@ export const SeverityField = ({ > - {isSeverityMappingChecked && ( + {(field.value as AboutStepSeverity).isMappingChecked && ( @@ -231,53 +242,72 @@ export const SeverityField = ({ - {options.map((option, index) => ( - - - - - + {(field.value as AboutStepSeverity).mapping.map( + (severityMappingItem: SeverityMappingItem, index) => ( + + + + + - - - - - - - - {option.inputDisplay} - - - - ))} + + + + + + + + { + options.find((o) => o.value === severityMappingItem.severity) + ?.inputDisplay + } + + + + ) + )} )} - - + + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts index f5d61553b595b..b9c3e4f84c18e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts @@ -5,6 +5,7 @@ */ import { AboutStepRule } from '../../../pages/detection_engine/rules/types'; +import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers'; export const threatDefault = [ { @@ -21,8 +22,8 @@ export const stepAboutDefaultValue: AboutStepRule = { isAssociatedToEndpointList: false, isBuildingBlock: false, isNew: true, - severity: { value: 'low', mapping: [] }, - riskScore: { value: 50, mapping: [] }, + severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, + riskScore: { value: 50, mapping: [], isMappingChecked: false }, references: [''], falsePositives: [''], license: '', diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx index a86c1b7ce1bea..cb3fd5e5bec32 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx @@ -16,6 +16,7 @@ import { stepAboutDefaultValue } from './default_value'; // we don't have the types for waitFor just yet, so using "as waitFor" until when we do import { wait as waitFor } from '@testing-library/react'; import { AboutStepRule } from '../../../pages/detection_engine/rules/types'; +import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers'; const theme = () => ({ eui: euiDarkVars, darkMode: true }); @@ -176,8 +177,8 @@ describe('StepAboutRuleComponent', () => { name: 'Test name text', note: '', references: [''], - riskScore: { value: 50, mapping: [] }, - severity: { value: 'low', mapping: [] }, + riskScore: { value: 50, mapping: [], isMappingChecked: false }, + severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, tags: [], threat: [ { @@ -236,8 +237,8 @@ describe('StepAboutRuleComponent', () => { name: 'Test name text', note: '', references: [''], - riskScore: { value: 80, mapping: [] }, - severity: { value: 'low', mapping: [] }, + riskScore: { value: 80, mapping: [], isMappingChecked: false }, + severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, tags: [], threat: [ { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx index fbd03850eee75..20470d7bb924f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx @@ -117,18 +117,16 @@ export const schema: FormSchema = { }, ], }, - mapping: { - type: FIELD_TYPES.TEXT, - }, + mapping: {}, + isMappingChecked: {}, }, riskScore: { value: { type: FIELD_TYPES.RANGE, serializer: (input: string) => Number(input), }, - mapping: { - type: FIELD_TYPES.TEXT, - }, + mapping: {}, + isMappingChecked: {}, }, references: { label: i18n.translate( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts index 14cf476e66563..7a6b61f0dfd89 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -9,6 +9,7 @@ import { Rule, RuleError } from '../../../../../containers/detection_engine/rule import { List } from '../../../../../../../common/detection_engine/schemas/types'; import { AboutStepRule, ActionsStepRule, DefineStepRule, ScheduleStepRule } from '../../types'; import { FieldValueQueryBar } from '../../../../../components/rules/query_bar'; +import { fillEmptySeverityMappings } from '../../helpers'; export const mockQueryBar: FieldValueQueryBar = { query: { @@ -175,8 +176,8 @@ export const mockAboutStepRule = (isNew = false): AboutStepRule => ({ license: 'Elastic License', name: 'Query with rule-id', description: '24/7', - severity: { value: 'low', mapping: [] }, - riskScore: { value: 21, mapping: [] }, + riskScore: { value: 21, mapping: [], isMappingChecked: false }, + severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, references: ['www.test.co'], falsePositives: ['test'], tags: ['tag1', 'tag2'], diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index 705013beb750f..8b03f62fc82bd 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -189,10 +189,14 @@ export const formatAboutStepData = ( false_positives: falsePositives.filter((item) => !isEmpty(item)), references: references.filter((item) => !isEmpty(item)), risk_score: riskScore.value, - risk_score_mapping: riskScore.mapping, + risk_score_mapping: riskScore.isMappingChecked + ? riskScore.mapping.filter((m) => m.field != null && m.field !== '') + : [], rule_name_override: ruleNameOverride !== '' ? ruleNameOverride : undefined, severity: severity.value, - severity_mapping: severity.mapping, + severity_mapping: severity.isMappingChecked + ? severity.mapping.filter((m) => m.field != null && m.field !== '' && m.value != null) + : [], threat: threat .filter((singleThreat) => singleThreat.tactic.name !== 'none') .map((singleThreat) => ({ diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx index b40243efcfb46..10a20807d6f87 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx @@ -17,6 +17,7 @@ import { getPrePackagedTimelineStatus, determineDetailsValue, userHasNoPermissions, + fillEmptySeverityMappings, } from './helpers'; import { mockRuleWithEverything, mockRule } from './all/__mocks__/mock'; import { esFilters } from '../../../../../../../../src/plugins/data/public'; @@ -97,9 +98,9 @@ describe('rule helpers', () => { name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], - riskScore: { value: 21, mapping: [] }, + riskScore: { value: 21, mapping: [], isMappingChecked: false }, ruleNameOverride: 'message', - severity: { value: 'low', mapping: [] }, + severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, tags: ['tag1', 'tag2'], threat: [ { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index 8f8967f2ff6d5..f9279ce639534 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -24,6 +24,8 @@ import { ScheduleStepRule, ActionsStepRule, } from './types'; +import { SeverityMapping } from '../../../../../common/detection_engine/schemas/common/schemas'; +import { severityOptions } from '../../../components/rules/step_about_rule/data'; export interface GetStepsData { aboutRuleData: AboutStepRule; @@ -150,18 +152,38 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu references, severity: { value: severity, - mapping: severityMapping, + mapping: fillEmptySeverityMappings(severityMapping), + isMappingChecked: severityMapping.length > 0, }, tags, riskScore: { value: riskScore, mapping: riskScoreMapping, + isMappingChecked: riskScoreMapping.length > 0, }, falsePositives, threat: threat as IMitreEnterpriseAttack[], }; }; +const severitySortMapping = { + low: 0, + medium: 1, + high: 2, + critical: 3, +}; + +export const fillEmptySeverityMappings = (mappings: SeverityMapping): SeverityMapping => { + const missingMappings: SeverityMapping = severityOptions.flatMap((so) => + mappings.find((mapping) => mapping.severity === so.value) == null + ? [{ field: '', value: '', operator: 'equals', severity: so.value }] + : [] + ); + return [...mappings, ...missingMappings].sort( + (a, b) => severitySortMapping[a.severity] - severitySortMapping[b.severity] + ); +}; + export const determineDetailsValue = ( rule: Rule, detailsView: boolean diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index 23715a88efc7b..e36f08703dae5 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -88,11 +88,13 @@ export interface AboutStepRuleDetails { export interface AboutStepSeverity { value: string; mapping: SeverityMapping; + isMappingChecked: boolean; } export interface AboutStepRiskScore { value: number; mapping: RiskScoreMapping; + isMappingChecked: boolean; } export interface DefineStepRule extends StepRuleData { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts index 356cf95fc0d24..888642f77af60 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts @@ -10,7 +10,6 @@ import { RiskScoreMappingOrUndefined, } from '../../../../../common/detection_engine/schemas/common/schemas'; import { SignalSourceHit } from '../types'; -import { RiskScore as RiskScoreIOTS } from '../../../../../common/detection_engine/schemas/types'; interface BuildRiskScoreFromMappingProps { doc: SignalSourceHit; @@ -33,8 +32,12 @@ export const buildRiskScoreFromMapping = ({ const mappedField = riskScoreMapping[0].field; // TODO: Expand by verifying fieldType from index via doc._index const mappedValue = get(mappedField, doc._source); - // TODO: This doesn't seem to validate...identified riskScore > 100 😬 - if (RiskScoreIOTS.is(mappedValue)) { + if ( + typeof mappedValue === 'number' && + Number.isSafeInteger(mappedValue) && + mappedValue >= 0 && + mappedValue <= 100 + ) { return { riskScore: mappedValue, riskScoreMeta: { riskScoreOverridden: true } }; } } From 48bb7c79c4af062dfdb1acb65dc4c0e1332a8824 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Tue, 28 Jul 2020 15:56:06 -0500 Subject: [PATCH 60/75] [Metrics UI] Fix filterQuery on Inventory alert preview (#73329) Co-authored-by: Elastic Machine --- .../infra/public/alerting/inventory/components/expression.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index 8d36262b55792..583cbe18ee9db 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -364,7 +364,7 @@ export const Expressions: React.FC = (props) => { Date: Tue, 28 Jul 2020 17:12:57 -0400 Subject: [PATCH 61/75] [Ingest Manager] Fix enrollment key selection (#73062) --- .../sections/fleet/agent_list_page/index.tsx | 4 +- .../config_selection.tsx | 147 +++++++++++------- 2 files changed, 92 insertions(+), 59 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx index c10378f48a8d1..c52f8447214a0 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx @@ -536,9 +536,9 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { ), }} /> - ) : !isLoading && totalAgents === 0 ? ( + ) : ( emptyPrompt - ) : undefined + ) } items={totalAgents ? agents : []} itemId="id" diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/config_selection.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/config_selection.tsx index 09b00240dc127..e98ebb7cadc7c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/config_selection.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/config_selection.tsx @@ -39,71 +39,105 @@ export const EnrollmentStepAgentConfig: React.FC = (props) => { enrollmentAPIKeyId?: string; }>({}); - useEffect(() => { - if (agentConfigs && agentConfigs.length && !selectedState.agentConfigId) { - setSelectedState({ - ...selectedState, - agentConfigId: agentConfigs[0].id, - }); - } - }, [agentConfigs, selectedState]); + useEffect( + function triggerOnConfigChangeEffect() { + if (onConfigChange && selectedState.agentConfigId) { + onConfigChange(selectedState.agentConfigId); + } + }, + [selectedState.agentConfigId, onConfigChange] + ); - useEffect(() => { - if (onConfigChange && selectedState.agentConfigId) { - onConfigChange(selectedState.agentConfigId); - } - }, [selectedState.agentConfigId, onConfigChange]); + useEffect( + function triggerOnKeyChangeEffect() { + if (!withKeySelection || !onKeyChange) { + return; + } - useEffect(() => { - if (!withKeySelection) { - return; - } - if (!selectedState.agentConfigId) { - setEnrollmentAPIKeys([]); - return; - } + if (selectedState.enrollmentAPIKeyId) { + onKeyChange(selectedState.enrollmentAPIKeyId); + } + }, + [withKeySelection, onKeyChange, selectedState.enrollmentAPIKeyId] + ); - async function fetchEnrollmentAPIKeys() { - try { - const res = await sendGetEnrollmentAPIKeys({ - page: 1, - perPage: 10000, - }); - if (res.error) { - throw res.error; + useEffect( + function useDefaultConfigEffect() { + if (agentConfigs && agentConfigs.length && !selectedState.agentConfigId) { + const defaultConfig = agentConfigs.find((config) => config.is_default); + if (defaultConfig) { + setSelectedState({ + ...selectedState, + agentConfigId: defaultConfig.id, + }); } + } + }, + [agentConfigs, selectedState] + ); - if (!res.data) { - throw new Error('No data while fetching enrollment API keys'); + useEffect( + function useEnrollmentKeysForConfigEffect() { + if (!withKeySelection) { + return; + } + if (!selectedState.agentConfigId) { + setEnrollmentAPIKeys([]); + return; + } + + async function fetchEnrollmentAPIKeys() { + try { + const res = await sendGetEnrollmentAPIKeys({ + page: 1, + perPage: 10000, + }); + if (res.error) { + throw res.error; + } + + if (!res.data) { + throw new Error('No data while fetching enrollment API keys'); + } + + setEnrollmentAPIKeys( + res.data.list.filter((key) => key.config_id === selectedState.agentConfigId) + ); + } catch (error) { + notifications.toasts.addError(error, { + title: 'Error', + }); } + } + fetchEnrollmentAPIKeys(); + }, + [withKeySelection, selectedState.agentConfigId, notifications.toasts] + ); - setEnrollmentAPIKeys( - res.data.list.filter((key) => key.config_id === selectedState.agentConfigId) - ); - } catch (error) { - notifications.toasts.addError(error, { - title: 'Error', + useEffect( + function useDefaultEnrollmentKeyForConfigEffect() { + if (!withKeySelection) { + return; + } + if ( + !selectedState.enrollmentAPIKeyId && + enrollmentAPIKeys.length > 0 && + enrollmentAPIKeys[0].config_id === selectedState.agentConfigId + ) { + const enrollmentAPIKeyId = enrollmentAPIKeys[0].id; + setSelectedState({ + agentConfigId: selectedState.agentConfigId, + enrollmentAPIKeyId, }); } - } - fetchEnrollmentAPIKeys(); - }, [withKeySelection, selectedState.agentConfigId, notifications.toasts]); - - // Select first API key when config change - React.useEffect(() => { - if (!withKeySelection || !onKeyChange) { - return; - } - if (!selectedState.enrollmentAPIKeyId && enrollmentAPIKeys.length > 0) { - const enrollmentAPIKeyId = enrollmentAPIKeys[0].id; - setSelectedState({ - agentConfigId: selectedState.agentConfigId, - enrollmentAPIKeyId, - }); - onKeyChange(enrollmentAPIKeyId); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [enrollmentAPIKeys, selectedState.enrollmentAPIKeyId, selectedState.agentConfigId]); + }, + [ + withKeySelection, + enrollmentAPIKeys, + selectedState.enrollmentAPIKeyId, + selectedState.agentConfigId, + ] + ); return ( <> @@ -174,7 +208,6 @@ export const EnrollmentStepAgentConfig: React.FC = (props) => { ...selectedState, enrollmentAPIKeyId: e.target.value, }); - onKeyChange(e.target.value); }} /> From 26229845524467a7b7bd6cab913cc8c7023a1bb3 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Tue, 28 Jul 2020 17:39:52 -0400 Subject: [PATCH 62/75] Re-enable test for create package configI believe it was supposed to be enabled with https://github.com/elastic/kibana/pull/71727/files We'll see what CI thinks (#73220) Co-authored-by: Elastic Machine --- .../apis/package_config/create.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/test/ingest_manager_api_integration/apis/package_config/create.ts b/x-pack/test/ingest_manager_api_integration/apis/package_config/create.ts index 27581550ac2bc..cae4ff79bdef6 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/package_config/create.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/package_config/create.ts @@ -18,9 +18,7 @@ export default function ({ getService }: FtrProviderContext) { // because `this` has to point to the Mocha context // see https://mochajs.org/#arrow-functions - // Temporarily skipped to promote snapshot - // Re-enabled in https://github.com/elastic/kibana/pull/71727 - describe.skip('Package Config - create', async function () { + describe('Package Config - create', async function () { let agentConfigId: string; before(async function () { From 3e637966115c3305812851dbb07f5870249d52a7 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Tue, 28 Jul 2020 17:49:17 -0400 Subject: [PATCH 63/75] [Canvas][tech-debt] Refactor Layout Annotations (kill recompose.pure Part 2) (#73305) --- .../alignment_guide/alignment_guide.js | 33 -------- .../alignment_guide/alignment_guide.scss | 4 - .../components/alignment_guide/index.js | 10 --- .../border_connection/border_connection.js | 27 ------- .../border_connection/border_connection.scss | 9 --- .../components/border_connection/index.js | 10 --- .../border_resize_handle.scss | 13 --- .../components/border_resize_handle/index.js | 10 --- .../dragbox_annotation/dragbox_annotation.js | 26 ------ .../dragbox_annotation.scss | 8 -- .../components/dragbox_annotation/index.js | 10 --- .../hover_annotation/hover_annotation.scss | 8 -- .../components/hover_annotation/index.js | 10 --- .../layout_annotations/alignment_guide.tsx | 37 +++++++++ .../layout_annotations/border_connection.tsx | 36 +++++++++ .../border_resize_handle.tsx} | 11 ++- .../layout_annotations/dragbox_annotation.tsx | 35 ++++++++ .../hover_annotation.tsx} | 33 +++++--- .../components/layout_annotations/index.ts | 13 +++ .../layout_annotations.scss | 81 +++++++++++++++++++ .../rotation_handle.tsx} | 15 +++- .../tooltip_annotation.tsx} | 12 ++- .../components/rotation_handle/index.js | 10 --- .../rotation_handle/rotation_handle.scss | 25 ------ .../components/tooltip_annotation/index.js | 10 --- .../tooltip_annotation.scss | 8 -- .../interactive_workpad_page.js | 16 ++-- x-pack/plugins/canvas/public/style/index.scss | 8 +- 28 files changed, 262 insertions(+), 266 deletions(-) delete mode 100644 x-pack/plugins/canvas/public/components/alignment_guide/alignment_guide.js delete mode 100644 x-pack/plugins/canvas/public/components/alignment_guide/alignment_guide.scss delete mode 100644 x-pack/plugins/canvas/public/components/alignment_guide/index.js delete mode 100644 x-pack/plugins/canvas/public/components/border_connection/border_connection.js delete mode 100644 x-pack/plugins/canvas/public/components/border_connection/border_connection.scss delete mode 100644 x-pack/plugins/canvas/public/components/border_connection/index.js delete mode 100644 x-pack/plugins/canvas/public/components/border_resize_handle/border_resize_handle.scss delete mode 100644 x-pack/plugins/canvas/public/components/border_resize_handle/index.js delete mode 100644 x-pack/plugins/canvas/public/components/dragbox_annotation/dragbox_annotation.js delete mode 100644 x-pack/plugins/canvas/public/components/dragbox_annotation/dragbox_annotation.scss delete mode 100644 x-pack/plugins/canvas/public/components/dragbox_annotation/index.js delete mode 100644 x-pack/plugins/canvas/public/components/hover_annotation/hover_annotation.scss delete mode 100644 x-pack/plugins/canvas/public/components/hover_annotation/index.js create mode 100644 x-pack/plugins/canvas/public/components/layout_annotations/alignment_guide.tsx create mode 100644 x-pack/plugins/canvas/public/components/layout_annotations/border_connection.tsx rename x-pack/plugins/canvas/public/components/{border_resize_handle/border_resize_handle.js => layout_annotations/border_resize_handle.tsx} (68%) create mode 100644 x-pack/plugins/canvas/public/components/layout_annotations/dragbox_annotation.tsx rename x-pack/plugins/canvas/public/components/{hover_annotation/hover_annotation.js => layout_annotations/hover_annotation.tsx} (50%) create mode 100644 x-pack/plugins/canvas/public/components/layout_annotations/index.ts create mode 100644 x-pack/plugins/canvas/public/components/layout_annotations/layout_annotations.scss rename x-pack/plugins/canvas/public/components/{rotation_handle/rotation_handle.js => layout_annotations/rotation_handle.tsx} (60%) rename x-pack/plugins/canvas/public/components/{tooltip_annotation/tooltip_annotation.js => layout_annotations/tooltip_annotation.tsx} (70%) delete mode 100644 x-pack/plugins/canvas/public/components/rotation_handle/index.js delete mode 100644 x-pack/plugins/canvas/public/components/rotation_handle/rotation_handle.scss delete mode 100644 x-pack/plugins/canvas/public/components/tooltip_annotation/index.js delete mode 100644 x-pack/plugins/canvas/public/components/tooltip_annotation/tooltip_annotation.scss diff --git a/x-pack/plugins/canvas/public/components/alignment_guide/alignment_guide.js b/x-pack/plugins/canvas/public/components/alignment_guide/alignment_guide.js deleted file mode 100644 index d5a76efaf3d49..0000000000000 --- a/x-pack/plugins/canvas/public/components/alignment_guide/alignment_guide.js +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { matrixToCSS } from '../../lib/dom'; - -export const AlignmentGuide = ({ transformMatrix, width, height }) => { - const newStyle = { - width, - height, - marginLeft: -width / 2, - marginTop: -height / 2, - background: 'magenta', - position: 'absolute', - transform: matrixToCSS(transformMatrix), - }; - return ( -
      - ); -}; - -AlignmentGuide.propTypes = { - transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, -}; diff --git a/x-pack/plugins/canvas/public/components/alignment_guide/alignment_guide.scss b/x-pack/plugins/canvas/public/components/alignment_guide/alignment_guide.scss deleted file mode 100644 index 27f06b42df453..0000000000000 --- a/x-pack/plugins/canvas/public/components/alignment_guide/alignment_guide.scss +++ /dev/null @@ -1,4 +0,0 @@ -.canvasAlignmentGuide { - transform-origin: center center; /* the default, only for clarity */ - transform-style: preserve-3d; -} diff --git a/x-pack/plugins/canvas/public/components/alignment_guide/index.js b/x-pack/plugins/canvas/public/components/alignment_guide/index.js deleted file mode 100644 index 6793e0151759b..0000000000000 --- a/x-pack/plugins/canvas/public/components/alignment_guide/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pure } from 'recompose'; -import { AlignmentGuide as Component } from './alignment_guide'; - -export const AlignmentGuide = pure(Component); diff --git a/x-pack/plugins/canvas/public/components/border_connection/border_connection.js b/x-pack/plugins/canvas/public/components/border_connection/border_connection.js deleted file mode 100644 index a7356e0a3f870..0000000000000 --- a/x-pack/plugins/canvas/public/components/border_connection/border_connection.js +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { matrixToCSS } from '../../lib/dom'; - -export const BorderConnection = ({ transformMatrix, width, height }) => { - const newStyle = { - width, - height, - marginLeft: -width / 2, - marginTop: -height / 2, - position: 'absolute', - transform: matrixToCSS(transformMatrix), - }; - return
      ; -}; - -BorderConnection.propTypes = { - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired, -}; diff --git a/x-pack/plugins/canvas/public/components/border_connection/border_connection.scss b/x-pack/plugins/canvas/public/components/border_connection/border_connection.scss deleted file mode 100644 index ba09edd1090db..0000000000000 --- a/x-pack/plugins/canvas/public/components/border_connection/border_connection.scss +++ /dev/null @@ -1,9 +0,0 @@ -.canvasBorder--connection { - position: absolute; - top: 0; - width: 100%; - height: 100%; - pointer-events: none; - border-top: 1px dashed $euiColorLightShade; - border-left: 1px dashed $euiColorLightShade; -} diff --git a/x-pack/plugins/canvas/public/components/border_connection/index.js b/x-pack/plugins/canvas/public/components/border_connection/index.js deleted file mode 100644 index b99ab923d52d4..0000000000000 --- a/x-pack/plugins/canvas/public/components/border_connection/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pure } from 'recompose'; -import { BorderConnection as Component } from './border_connection'; - -export const BorderConnection = pure(Component); diff --git a/x-pack/plugins/canvas/public/components/border_resize_handle/border_resize_handle.scss b/x-pack/plugins/canvas/public/components/border_resize_handle/border_resize_handle.scss deleted file mode 100644 index 4913a599b07f7..0000000000000 --- a/x-pack/plugins/canvas/public/components/border_resize_handle/border_resize_handle.scss +++ /dev/null @@ -1,13 +0,0 @@ -.canvasBorderResizeHandle { - @include euiSlightShadow; - transform-origin: center center; /* the default, only for clarity */ - transform-style: preserve-3d; - display: block; - position: absolute; - height: 8px; - width: 8px; - margin-left: -4px; - margin-top: -4px; - background-color: $euiColorEmptyShade; - border: 1px solid $euiColorDarkShade; -} diff --git a/x-pack/plugins/canvas/public/components/border_resize_handle/index.js b/x-pack/plugins/canvas/public/components/border_resize_handle/index.js deleted file mode 100644 index c3fea05d60f7e..0000000000000 --- a/x-pack/plugins/canvas/public/components/border_resize_handle/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pure } from 'recompose'; -import { BorderResizeHandle as Component } from './border_resize_handle'; - -export const BorderResizeHandle = pure(Component); diff --git a/x-pack/plugins/canvas/public/components/dragbox_annotation/dragbox_annotation.js b/x-pack/plugins/canvas/public/components/dragbox_annotation/dragbox_annotation.js deleted file mode 100644 index 8c67404cb9b7d..0000000000000 --- a/x-pack/plugins/canvas/public/components/dragbox_annotation/dragbox_annotation.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { matrixToCSS } from '../../lib/dom'; - -export const DragBoxAnnotation = ({ transformMatrix, width, height }) => { - const newStyle = { - width, - height, - marginLeft: -width / 2, - marginTop: -height / 2, - transform: matrixToCSS(transformMatrix), - }; - return
      ; -}; - -DragBoxAnnotation.propTypes = { - transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, -}; diff --git a/x-pack/plugins/canvas/public/components/dragbox_annotation/dragbox_annotation.scss b/x-pack/plugins/canvas/public/components/dragbox_annotation/dragbox_annotation.scss deleted file mode 100644 index bd14ced586dbd..0000000000000 --- a/x-pack/plugins/canvas/public/components/dragbox_annotation/dragbox_annotation.scss +++ /dev/null @@ -1,8 +0,0 @@ -.canvasDragBoxAnnotation { - position: absolute; - background: none; - transform-origin: center center; /* the default, only for clarity */ - transform-style: preserve-3d; - outline: dashed 1px $euiColorDarkShade; - pointer-events: none; -} diff --git a/x-pack/plugins/canvas/public/components/dragbox_annotation/index.js b/x-pack/plugins/canvas/public/components/dragbox_annotation/index.js deleted file mode 100644 index 85c97e90776c6..0000000000000 --- a/x-pack/plugins/canvas/public/components/dragbox_annotation/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pure } from 'recompose'; -import { DragBoxAnnotation as Component } from './dragbox_annotation'; - -export const DragBoxAnnotation = pure(Component); diff --git a/x-pack/plugins/canvas/public/components/hover_annotation/hover_annotation.scss b/x-pack/plugins/canvas/public/components/hover_annotation/hover_annotation.scss deleted file mode 100644 index 4771bbb6134be..0000000000000 --- a/x-pack/plugins/canvas/public/components/hover_annotation/hover_annotation.scss +++ /dev/null @@ -1,8 +0,0 @@ -.canvasHoverAnnotation { - position: absolute; - background: none; - transform-origin: center center; /* the default, only for clarity */ - transform-style: preserve-3d; - outline: solid 1px $euiColorVis0; - pointer-events: none; -} diff --git a/x-pack/plugins/canvas/public/components/hover_annotation/index.js b/x-pack/plugins/canvas/public/components/hover_annotation/index.js deleted file mode 100644 index 71c57a25d7960..0000000000000 --- a/x-pack/plugins/canvas/public/components/hover_annotation/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pure } from 'recompose'; -import { HoverAnnotation as Component } from './hover_annotation'; - -export const HoverAnnotation = pure(Component); diff --git a/x-pack/plugins/canvas/public/components/layout_annotations/alignment_guide.tsx b/x-pack/plugins/canvas/public/components/layout_annotations/alignment_guide.tsx new file mode 100644 index 0000000000000..5ac23ab41f7c0 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/layout_annotations/alignment_guide.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import PropTypes from 'prop-types'; +import { matrixToCSS } from '../../lib/dom'; +import { TransformMatrix3d } from '../../lib/aeroelastic'; + +interface Props { + height: number; + transformMatrix: TransformMatrix3d; + width: number; +} + +export const AlignmentGuide: FC = ({ transformMatrix, width, height }) => ( +
      +); + +AlignmentGuide.propTypes = { + height: PropTypes.number.isRequired, + transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired, + width: PropTypes.number.isRequired, +}; diff --git a/x-pack/plugins/canvas/public/components/layout_annotations/border_connection.tsx b/x-pack/plugins/canvas/public/components/layout_annotations/border_connection.tsx new file mode 100644 index 0000000000000..e47ec3cc89e7b --- /dev/null +++ b/x-pack/plugins/canvas/public/components/layout_annotations/border_connection.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import PropTypes from 'prop-types'; +import { matrixToCSS } from '../../lib/dom'; +import { TransformMatrix3d } from '../../lib/aeroelastic'; + +interface Props { + height: number; + transformMatrix: TransformMatrix3d; + width: number; +} + +export const BorderConnection: FC = ({ transformMatrix, width, height }) => ( +
      +); + +BorderConnection.propTypes = { + height: PropTypes.number.isRequired, + transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired, + width: PropTypes.number.isRequired, +}; diff --git a/x-pack/plugins/canvas/public/components/border_resize_handle/border_resize_handle.js b/x-pack/plugins/canvas/public/components/layout_annotations/border_resize_handle.tsx similarity index 68% rename from x-pack/plugins/canvas/public/components/border_resize_handle/border_resize_handle.js rename to x-pack/plugins/canvas/public/components/layout_annotations/border_resize_handle.tsx index de9d573724836..f7c49205568fe 100644 --- a/x-pack/plugins/canvas/public/components/border_resize_handle/border_resize_handle.js +++ b/x-pack/plugins/canvas/public/components/layout_annotations/border_resize_handle.tsx @@ -4,11 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { matrixToCSS } from '../../lib/dom'; +import { TransformMatrix3d } from '../../lib/aeroelastic'; -export const BorderResizeHandle = ({ transformMatrix, zoomScale }) => ( +interface Props { + transformMatrix: TransformMatrix3d; + zoomScale?: number; +} + +export const BorderResizeHandle: FC = ({ transformMatrix, zoomScale = 1 }) => (
      ( BorderResizeHandle.propTypes = { transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired, + zoomScale: PropTypes.number, }; diff --git a/x-pack/plugins/canvas/public/components/layout_annotations/dragbox_annotation.tsx b/x-pack/plugins/canvas/public/components/layout_annotations/dragbox_annotation.tsx new file mode 100644 index 0000000000000..16a2a626b7975 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/layout_annotations/dragbox_annotation.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import PropTypes from 'prop-types'; +import { matrixToCSS } from '../../lib/dom'; +import { TransformMatrix3d } from '../../lib/aeroelastic'; + +interface Props { + height: number; + transformMatrix: TransformMatrix3d; + width: number; +} + +export const DragBoxAnnotation: FC = ({ transformMatrix, width, height }) => ( +
      +); + +DragBoxAnnotation.propTypes = { + transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, +}; diff --git a/x-pack/plugins/canvas/public/components/hover_annotation/hover_annotation.js b/x-pack/plugins/canvas/public/components/layout_annotations/hover_annotation.tsx similarity index 50% rename from x-pack/plugins/canvas/public/components/hover_annotation/hover_annotation.js rename to x-pack/plugins/canvas/public/components/layout_annotations/hover_annotation.tsx index 7337c0446e31c..a8d73f8bf9ec7 100644 --- a/x-pack/plugins/canvas/public/components/hover_annotation/hover_annotation.js +++ b/x-pack/plugins/canvas/public/components/layout_annotations/hover_annotation.tsx @@ -4,23 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { matrixToCSS } from '../../lib/dom'; +import { TransformMatrix3d } from '../../lib/aeroelastic'; -export const HoverAnnotation = ({ transformMatrix, width, height }) => { - const newStyle = { - width, - height, - marginLeft: -width / 2, - marginTop: -height / 2, - transform: matrixToCSS(transformMatrix), - }; - return
      ; -}; +interface Props { + height: number; + transformMatrix: TransformMatrix3d; + width: number; +} + +export const HoverAnnotation: FC = ({ transformMatrix, width, height }) => ( +
      +); HoverAnnotation.propTypes = { + height: PropTypes.number.isRequired, transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired, width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, }; diff --git a/x-pack/plugins/canvas/public/components/layout_annotations/index.ts b/x-pack/plugins/canvas/public/components/layout_annotations/index.ts new file mode 100644 index 0000000000000..d2334c6a225fe --- /dev/null +++ b/x-pack/plugins/canvas/public/components/layout_annotations/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AlignmentGuide } from './alignment_guide'; +export { DragBoxAnnotation } from './dragbox_annotation'; +export { HoverAnnotation } from './hover_annotation'; +export { TooltipAnnotation } from './tooltip_annotation'; +export { RotationHandle } from './rotation_handle'; +export { BorderConnection } from './border_connection'; +export { BorderResizeHandle } from './border_resize_handle'; diff --git a/x-pack/plugins/canvas/public/components/layout_annotations/layout_annotations.scss b/x-pack/plugins/canvas/public/components/layout_annotations/layout_annotations.scss new file mode 100644 index 0000000000000..363aa1f241b9d --- /dev/null +++ b/x-pack/plugins/canvas/public/components/layout_annotations/layout_annotations.scss @@ -0,0 +1,81 @@ +.canvasAlignmentGuide { + transform-origin: center center; /* the default, only for clarity */ + transform-style: preserve-3d; +} + +.canvasBorderConnection { + position: absolute; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; + border-top: 1px dashed $euiColorLightShade; + border-left: 1px dashed $euiColorLightShade; +} + +.canvasBorderResizeHandle { + @include euiSlightShadow; + transform-origin: center center; /* the default, only for clarity */ + transform-style: preserve-3d; + display: block; + position: absolute; + height: $euiSizeS; + width: $euiSizeS; + margin-left: -4px; + margin-top: -4px; + background-color: $euiColorEmptyShade; + border: 1px solid $euiColorDarkShade; +} + +.canvasDragBoxAnnotation { + position: absolute; + background: none; + transform-origin: center center; /* the default, only for clarity */ + transform-style: preserve-3d; + outline: dashed 1px $euiColorDarkShade; + pointer-events: none; +} + +.canvasHoverAnnotation { + position: absolute; + background: none; + transform-origin: center center; /* the default, only for clarity */ + transform-style: preserve-3d; + outline: solid 1px $euiColorVis0; + pointer-events: none; +} + +.canvasRotationHandle { + transform-origin: center center; /* the default, only for clarity */ + transform-style: preserve-3d; + display: block; + position: absolute; + height: $euiSizeL; + width: 0; + margin-left: -1px; + margin-top: -12px; + border-top: 1px dashed $euiColorLightShade; + border-left: 1px dashed $euiColorLightShade; +} + +.canvasRotationHandle__handle { + transform-origin: center center; /* the default, only for clarity */ + transform-style: preserve-3d; + display: block; + position: absolute; + height: 9px; + width: 9px; + margin-left: -5px; + margin-top: -6px; + border-radius: 50%; + background-color: $euiColorMediumShade; +} + +.tooltipAnnotation { + @include euiToolTipStyle($size: 's'); + position: absolute; + transform-origin: center center; /* the default, only for clarity */ + transform-style: preserve-3d; + outline: none; + pointer-events: none; +} diff --git a/x-pack/plugins/canvas/public/components/rotation_handle/rotation_handle.js b/x-pack/plugins/canvas/public/components/layout_annotations/rotation_handle.tsx similarity index 60% rename from x-pack/plugins/canvas/public/components/rotation_handle/rotation_handle.js rename to x-pack/plugins/canvas/public/components/layout_annotations/rotation_handle.tsx index dfadbbc39c547..e2a3f57efc672 100644 --- a/x-pack/plugins/canvas/public/components/rotation_handle/rotation_handle.js +++ b/x-pack/plugins/canvas/public/components/layout_annotations/rotation_handle.tsx @@ -4,19 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { matrixToCSS } from '../../lib/dom'; +import { TransformMatrix3d } from '../../lib/aeroelastic'; -export const RotationHandle = ({ transformMatrix, zoomScale }) => ( +interface Props { + transformMatrix: TransformMatrix3d; + zoomScale?: number; +} + +export const RotationHandle: FC = ({ transformMatrix, zoomScale = 1 }) => (
      @@ -24,4 +30,5 @@ export const RotationHandle = ({ transformMatrix, zoomScale }) => ( RotationHandle.propTypes = { transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired, + zoomScale: PropTypes.number, }; diff --git a/x-pack/plugins/canvas/public/components/tooltip_annotation/tooltip_annotation.js b/x-pack/plugins/canvas/public/components/layout_annotations/tooltip_annotation.tsx similarity index 70% rename from x-pack/plugins/canvas/public/components/tooltip_annotation/tooltip_annotation.js rename to x-pack/plugins/canvas/public/components/layout_annotations/tooltip_annotation.tsx index 1836bfd0162f3..84c527bf7af27 100644 --- a/x-pack/plugins/canvas/public/components/tooltip_annotation/tooltip_annotation.js +++ b/x-pack/plugins/canvas/public/components/layout_annotations/tooltip_annotation.tsx @@ -4,11 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { matrixToCSS } from '../../lib/dom'; +import { TransformMatrix3d } from '../../lib/aeroelastic'; -export const HoverAnnotation = ({ transformMatrix, text }) => { +interface Props { + transformMatrix: TransformMatrix3d; + text: string; +} + +export const TooltipAnnotation: FC = ({ transformMatrix, text }) => { const newStyle = { transform: `${matrixToCSS(transformMatrix)} translate(1em, -1em)`, }; @@ -19,7 +25,7 @@ export const HoverAnnotation = ({ transformMatrix, text }) => { ); }; -HoverAnnotation.propTypes = { +TooltipAnnotation.propTypes = { transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired, text: PropTypes.string.isRequired, }; diff --git a/x-pack/plugins/canvas/public/components/rotation_handle/index.js b/x-pack/plugins/canvas/public/components/rotation_handle/index.js deleted file mode 100644 index 86c99ce12a04e..0000000000000 --- a/x-pack/plugins/canvas/public/components/rotation_handle/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pure } from 'recompose'; -import { RotationHandle as Component } from './rotation_handle'; - -export const RotationHandle = pure(Component); diff --git a/x-pack/plugins/canvas/public/components/rotation_handle/rotation_handle.scss b/x-pack/plugins/canvas/public/components/rotation_handle/rotation_handle.scss deleted file mode 100644 index 233a86199c483..0000000000000 --- a/x-pack/plugins/canvas/public/components/rotation_handle/rotation_handle.scss +++ /dev/null @@ -1,25 +0,0 @@ -.canvasRotationHandle--connector { - transform-origin: center center; /* the default, only for clarity */ - transform-style: preserve-3d; - display: block; - position: absolute; - height: 24px; - width: 0; - margin-left: -1px; - margin-top: -12px; - border-top: 1px dashed $euiColorLightShade; - border-left: 1px dashed $euiColorLightShade; -} - -.canvasRotationHandle--handle { - transform-origin: center center; /* the default, only for clarity */ - transform-style: preserve-3d; - display: block; - position: absolute; - height: 9px; - width: 9px; - margin-left: -5px; - margin-top: -6px; - border-radius: 50%; - background-color: $euiColorMediumShade; -} diff --git a/x-pack/plugins/canvas/public/components/tooltip_annotation/index.js b/x-pack/plugins/canvas/public/components/tooltip_annotation/index.js deleted file mode 100644 index c6d545be560ce..0000000000000 --- a/x-pack/plugins/canvas/public/components/tooltip_annotation/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pure } from 'recompose'; -import { HoverAnnotation as Component } from './tooltip_annotation'; - -export const TooltipAnnotation = pure(Component); diff --git a/x-pack/plugins/canvas/public/components/tooltip_annotation/tooltip_annotation.scss b/x-pack/plugins/canvas/public/components/tooltip_annotation/tooltip_annotation.scss deleted file mode 100644 index d91e49c0c628f..0000000000000 --- a/x-pack/plugins/canvas/public/components/tooltip_annotation/tooltip_annotation.scss +++ /dev/null @@ -1,8 +0,0 @@ -.tooltipAnnotation { - @include euiToolTipStyle($size: 's'); - position: absolute; - transform-origin: center center; /* the default, only for clarity */ - transform-style: preserve-3d; - outline: none; - pointer-events: none; -} diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js index 152da323e89ea..4089a1d709299 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js @@ -6,13 +6,15 @@ import React, { PureComponent } from 'react'; import { ElementWrapper } from '../../element_wrapper'; -import { AlignmentGuide } from '../../alignment_guide'; -import { DragBoxAnnotation } from '../../dragbox_annotation'; -import { HoverAnnotation } from '../../hover_annotation'; -import { TooltipAnnotation } from '../../tooltip_annotation'; -import { RotationHandle } from '../../rotation_handle'; -import { BorderConnection } from '../../border_connection'; -import { BorderResizeHandle } from '../../border_resize_handle'; +import { + AlignmentGuide, + DragBoxAnnotation, + HoverAnnotation, + TooltipAnnotation, + RotationHandle, + BorderConnection, + BorderResizeHandle, +} from '../../layout_annotations'; import { WorkpadShortcuts } from '../../workpad_shortcuts'; import { interactiveWorkpadPagePropTypes } from '../prop_types'; import { InteractionBoundary } from './interaction_boundary'; diff --git a/x-pack/plugins/canvas/public/style/index.scss b/x-pack/plugins/canvas/public/style/index.scss index ccaf6f1c1a478..3937d7fc05544 100644 --- a/x-pack/plugins/canvas/public/style/index.scss +++ b/x-pack/plugins/canvas/public/style/index.scss @@ -8,15 +8,12 @@ @import '../apps/export/export/export_app'; // Canvas components -@import '../components/alignment_guide/alignment_guide'; @import '../components/arg_add/arg_add'; @import '../components/arg_add_popover/arg_add_popover'; @import '../components/arg_form/arg_form'; @import '../components/asset_manager/asset_manager'; @import '../components/asset_picker/asset_picker'; @import '../components/autocomplete/autocomplete'; -@import '../components/border_connection/border_connection'; -@import '../components/border_resize_handle/border_resize_handle'; @import '../components/clipboard/clipboard'; @import '../components/color_dot/color_dot'; @import '../components/color_palette/color_palette'; @@ -27,25 +24,22 @@ @import '../components/datatable/datatable'; @import '../components/debug/debug'; @import '../components/dom_preview/dom_preview'; -@import '../components/dragbox_annotation/dragbox_annotation'; @import '../components/element_card/element_card'; @import '../components/element_content/element_content'; @import '../components/expression/expression'; @import '../components/fullscreen/fullscreen'; @import '../components/function_form/function_form'; -@import '../components/hover_annotation/hover_annotation'; +@import '../components/layout_annotations/layout_annotations'; @import '../components/loading/loading'; @import '../components/navbar/navbar'; @import '../components/page_manager/page_manager'; @import '../components/positionable/positionable'; -@import '../components/rotation_handle/rotation_handle'; @import '../components/shape_preview/shape_preview'; @import '../components/shape_picker/shape_picker'; @import '../components/sidebar/sidebar'; @import '../components/sidebar_header/sidebar_header'; @import '../components/toolbar/toolbar'; @import '../components/toolbar/tray/tray'; -@import '../components/tooltip_annotation/tooltip_annotation'; @import '../components/workpad/workpad'; @import '../components/workpad_header/element_menu/element_menu'; @import '../components/workpad_header/share_menu/share_menu'; From d57a379aa9ef2dff59eb2538a8091c2f83a61a95 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Tue, 28 Jul 2020 16:03:41 -0600 Subject: [PATCH 64/75] Fixes cypress build by removing alerting version within the saved object that causes migration issue while the cypress tests run (#73550) --- .../es_archives/export_rule/data.json.gz | Bin 1931 -> 1924 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/x-pack/test/security_solution_cypress/es_archives/export_rule/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/export_rule/data.json.gz index 373251d9e4f93d8097029345aa45aba959dd7a08..aad07a0bf6d53d4f427665ac312f45a0ef13b028 100644 GIT binary patch literal 1924 zcmV-~2YdJ*iwFq$hag`717u-zVJ>QOZ*Bn9SxIx-IugG7S8#NCODK`zuBmtM7TeE@ z?8uHCPfe*n5|m(pO9;T0oyvco21UuFHY-XplTq0Rp&O0vuNQPfUv?Cw!JjkS=qQaa z4I1y{!Xu(Wu6%(n@Rfv;iYU%`2bqr34iQq3fg{y4k)gVpVW~FqL*0+su4kY^h}@Ti zcr3RzN5n@7>g!VqOGO^Mo}iS{D+_Wd;3Q2LEdFb@`0sZWy4-RNx}OyKF@obJBZCA{ z)~K>&2h;7P$*= z*xk`>?_%uTc1JuGXWfxy>giE;uXjL=55w+-mpn$=-kmUT(6aZg4y-Hk@R^FxFz#EK znXr$R*&AFv^p08&SI4HHM_0CXdin57Ki@Dby1Bd={PowOmEETV=h9S65#uDNas+^- zh$m!;RgIQo#BsPQJ z-{8s9SgMw!PpV;StgaV{0cYu(MGsUmwko{{u2}s;2`%6a3HKo;)CCew z!U1?}kpRZv)22d;Di;_Iw3~m zURhW=imqvOBIal=+A3bq<9SufS}0SnkFu;Ua+wW{A?I1A)q>bVbeJ;UvFx_i0)`gD zfyfDeZiSfRvL;qUeywSfq-vby2^;l-G?$85+H%r4SPHqfUJhj0q}J=c<{~Xb2pk zZ`vku1J4OUv$3%E0&M6$;yfqeQj8dSgbEZcC1KbHK9Gmg^r^9wxQPvXC@bfB77}Ah z61-fKDfOCgy3`8E07W#;)|txFvP9G^nRWu77n1{ou}RU>XUmn)Yp`I5LqYLeDnLnr ziUkdY6l=wewR^*B#6}Yjo0jQ%0oK&Oc3oB1F?1Z3*H$$HMJR|ITi2p$)#qq1AB2s5 zzc?I1yf59~Ct>H?1E(TnRy#XI<$SfJdOLxGQ@+eK4UvV5cjnF<8q0HPcEHR-lYVKo zT*~gF6*suubRD?C3-+%&^8f43&X>)bzu9%BQn=1kG)PjZeg}$smI6S1xx7u^w?c5e z6YMW|w~Bb#i>$QWY`V6+tb6JEtJ;_OTYkc!9G2E9hf}SUvsb%|ozjYaiP;itUU{at z&l6|e&E{vWMTZmI_rcx~&P%;?Jq&%v4%9aCeAV<_P4(^2P(w#Ikb^92_-0Lfsp-_7 z#$rs*A-Qc(uL_2Z!yH`gXx|a$&?84=(6O6ZQ&SPmhDhHgs3&6TF7;oM~84)>tdsI0n0a;LZ zit0qO?O~VQ+^juRaWa4oL6w)DT29fB9@uic_9hqVH#zJr$lug#o1uT@9V-RzN|s3% z@eQvPv+Wh(&>;0uz2jcuy0NbsQ&WCBM*-ZADI5M%If15; zDzKn*d&(UQjeXth+rKNjSw`_K_D>0KS%%XzY~{DpV;lLlDNe zlvGBqdVwx5uzQ#5AwRv?}&&3RNLyfPAe-OMLurws+%w7$b9FlxcL zUc_2oFz`jc@;rfPr|tUKh>Raz?%p_eu9#(6`8HXO@V%4@$=Iok^3^9Wk_e7jH|ar# zie>gd9vhxzS3cFwArY~U^KP^cDvhL1E5r5c9Jt&8Zqu`k`|anCH*nZE5UP6olt6H- zz&Gq)BoKy>)o*!R_iVH3arXy#yqa?VOOH!E?4Rjz&q5l~{*OGa>Gs^`2f5s9dan5k zxV);z5AJWldM=>7wLQz!f2HHJV>zF{ zM})%AQvBAlntYBKo4k}S;WbxI literal 1931 zcmV;62Xy!!iwFpN;}Kr~17u-zVJ>QOZ*Bn9Sy@x#Iu?HCS9tPtmv&;ud#3IKTLYIR z&ZcOR0rP zQ2QVk9uO6B(g59I2*>4As>POSO?7>VDL8Jp<)Rq+A^0 zu@tQjiH~B`)29@ch%9*7K`FGC7UWXENfI+y{MT&p-yceJDbfYoPjdSh!J|4O{TNYJ zE3>8K6hBFhqlA3Ai*BMXp9W(Z96u+2?j~VpzkhL^-UiX({gZwijjfxZ?uQpAV*Gr0 zeE1wb+~2y*L(R&_ooziLqEQx~efcax_bG0CaW9W{@BGUP-S{*5tdYUrr{ntuxec$` z?a@u=V(i_thkPW?+C$6KlcV-t=YSfY2JH(keu%WaTVdd!VeefXSXbo!D;1$(+%-}& zW}hvy)4#g!95wE*j!i+2u59h}^8Sf_y=GQ)eR#s#G+mA8Mq^p=B#z|1-2mnhl znvf+{6SI0d@qdZ*?$7zyrB@+~vw@y)pMy!%b5+A__NmkKW(F!cS zAT%U#e~%`?usf-VSWsGF3NZ^ZlFH0nZG!}09N?Uboe{>@?wbQ~nJ~i117_=43FSC@ zg(pi!(zG~vR1I5WRlCRtaF(%Ij6kVk%i8ng8mWJ%JVP;?6y+Zkr8&GI;XdSqxv#fFjq$1JB-2_rP_ z6pf{&=$cj~VvgprtrP`)G;eCr3uOuRP@48cCaa+~;5={?q zaj#`K!Wo8gU7wK|VZ%<4WYRE8TTVI$PoehK>wzkp^m^UbT%-j})oq%FYT5=?y(WD1 zP1{6n;5k8P))r(hz=!q&&N31%#fYH?=s@985(YVlf!v%XkF}-5O?==3**Vv*|4QoDrHrCvw|XriNZovS=4T13^8=_K%Vu{b~(n>0Orwp>cR8Vd$E6co>u0<;w9 zSTImXwbrP%RyOP+Hk?G*v`p6vu%-sK>#DkrVdAj7rm7hzLP6x%x)znYK1coeB&_v% z`R0(~JsJKU30rSZoRX4R?`-9r^Tn6)=>!2z__EN{L>exhnLBf6)JCMAAz9>~XMmSy zqGC*{Id!kDElMw~$eDSw_)gT!mJ7>zXvO(&*IftB|D65%j{N_=v-559(Q$SdsuT`G z74_ppI^Tk$Ax!|@o?PB$YFweXidk_BZI&sR;~ z)l}aO4K;Lh13AdThHqBX7nV-_sV!E-9Fp56^{QmpIL#r{j`of;hY33({gz$V>Y9pZ zIzak1Nj*_h&Eb4Dy%2Jwa827OCmXo*G08@VxA?Q%;kvv3)~+(!@ttj@(6cpw3`RWM zs!k(sf^NN=xq7L|uG?F9ITbHyoU8QifG`FA_y8+~7fLSmk4k$WEWaFmgf=2F{Q5=b zpa5jS;3=vT%eIGI&VsYyQpIr}<_lFGnQCzdhw{Ld<25%qj^7lp*C2n@vTdFWR-VvO z@vdZ<%ph-g>zIj`l*4${L)CeGN$6hpaO3{vyz`hY`j;#F*H36%o~Ajv=QSxre^A8BCHKFKxU|FmnGyFaq#^D9DB_xK&wYMS z$i2Gfn!iBE%Xa+W`4+tA0>)e0vrPS08gD0t&1J>*qp2_Ga6f;y=t9g7#AMD|Jt+54 zx;P%o`}s#iC|p|dUy4?f&oN_@clISbXGt>LL&5L>EqSWWIXWT$$k>XXqncXXGo8wl zOiAcv>l%|QIK*L@m0^?xgWZVZY&8_Xr)zarTmHm1i_MB1a&qC_YV)_b5)?acdLxiq RS$ef{{sZ~)cm47m007H2!|VV6 From e5fd1c4c7fc2ca8cac34776199bfa548bc54d3ff Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Wed, 29 Jul 2020 01:29:48 +0300 Subject: [PATCH 65/75] [i18n] explicit process.exit(); call for i18n_integrate cli command (#73495) Co-authored-by: Elastic Machine --- src/dev/run_i18n_integrate.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dev/run_i18n_integrate.ts b/src/dev/run_i18n_integrate.ts index 23d66fae9f26e..ac1e957adfc99 100644 --- a/src/dev/run_i18n_integrate.ts +++ b/src/dev/run_i18n_integrate.ts @@ -108,6 +108,7 @@ run( const reporter = new ErrorReporter(); const messages: Map = new Map(); await list.run({ messages, reporter }); + process.exitCode = 0; } catch (error) { process.exitCode = 1; if (error instanceof ErrorReporter) { @@ -117,6 +118,7 @@ run( log.error(error); } } + process.exit(); }, { flags: { From 93d45fc6ff5d5e0a8a01b81464f6f36eb6ab7883 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 28 Jul 2020 18:49:05 -0400 Subject: [PATCH 66/75] [Ingest Manager] Update fleet instructions to run agent as a service (#73491) --- .../enrollment_instructions/manual/index.tsx | 85 ++++++++++++++----- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 3 files changed, 65 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx index 78f4f73cf18be..fe11c4cb08d13 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx @@ -5,7 +5,8 @@ */ import React from 'react'; -import { EuiText, EuiSpacer, EuiCode, EuiCodeBlock, EuiCopy, EuiButton } from '@elastic/eui'; +import styled from 'styled-components'; +import { EuiText, EuiSpacer, EuiCode, EuiTitle, EuiCodeBlock } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { EnrollmentAPIKey } from '../../../types'; @@ -15,42 +16,86 @@ interface Props { kibanaCASha256?: string; } +// Otherwise the copy button is over the text +const CommandCode = styled.pre({ + overflow: 'scroll', +}); + export const ManualInstructions: React.FunctionComponent = ({ kibanaUrl, apiKey, kibanaCASha256, }) => { - const command = ` -./elastic-agent enroll ${kibanaUrl} ${apiKey.api_key}${ + const enrollArgs = `${kibanaUrl} ${apiKey.api_key}${ kibanaCASha256 ? ` --ca_sha256=${kibanaCASha256}` : '' - } + }`; + const macOsLinuxTarCommand = `./elastic-agent enroll ${enrollArgs} ./elastic-agent run`; + + const linuxDebRpmCommand = `./elastic-agent enroll ${enrollArgs} +systemctl enable elastic-agent +systemctl start elastic-agent`; + + const windowsCommand = `./elastic-agent enroll ${enrollArgs} +./install-service-elastic-agent.ps1`; + return ( <> + + + +

      + +

      +
      + + + {windowsCommand} + + + +

      + +

      +
      + + + {linuxDebRpmCommand} + + + +

      + +

      +
      + + + agent enroll, + command: ./elastic-agent run, }} /> - - -
      {command}
      + + + {macOsLinuxTarCommand} - - - {(copy) => ( - - - - )} - ); }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ee7d1e0298d00..b6aaa2065c795 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8177,8 +8177,6 @@ "xpack.ingestManager.editPackageConfig.updatedNotificationTitle": "正常に'{packageConfigName}'を更新しました", "xpack.ingestManager.enrollemntAPIKeyList.emptyMessage": "登録トークンが見つかりません。", "xpack.ingestManager.enrollemntAPIKeyList.loadingTokensMessage": "登録トークンを読み込んでいます...", - "xpack.ingestManager.enrollmentInstructions.copyButton": "コマンドをコピー", - "xpack.ingestManager.enrollmentInstructions.descriptionText": "エージェントのディレクトリから、これらのコマンドを実行して、Elasticエージェントを登録して起動します。{enrollCommand}はエージェントの構成ファイルに書き込み、正しい設定になるようにします。このコマンドを使用すると、複数のホストでエージェントを設定できます。", "xpack.ingestManager.enrollmentStepAgentConfig.configSelectAriaLabel": "エージェント構成", "xpack.ingestManager.enrollmentStepAgentConfig.configSelectLabel": "エージェント構成", "xpack.ingestManager.enrollmentStepAgentConfig.enrollmentTokenSelectLabel": "登録トークン", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 30c932c362a4f..dcbcda120587f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8182,8 +8182,6 @@ "xpack.ingestManager.editPackageConfig.updatedNotificationTitle": "已成功更新“{packageConfigName}”", "xpack.ingestManager.enrollemntAPIKeyList.emptyMessage": "未找到任何注册令牌。", "xpack.ingestManager.enrollemntAPIKeyList.loadingTokensMessage": "正在加载注册令牌......", - "xpack.ingestManager.enrollmentInstructions.copyButton": "复制命令", - "xpack.ingestManager.enrollmentInstructions.descriptionText": "从代理的目录,运行这些命令以注册并启动 Elastic 代理。{enrollCommand} 将写入代理的配置文件,以便其具有正确的设置。可以使用此命令在多个主机上设置代理。", "xpack.ingestManager.enrollmentStepAgentConfig.configSelectAriaLabel": "代理配置", "xpack.ingestManager.enrollmentStepAgentConfig.configSelectLabel": "代理配置", "xpack.ingestManager.enrollmentStepAgentConfig.enrollmentTokenSelectLabel": "注册令牌", From 7a3e800aaab50969f7d53ac77f781a2544463f06 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Tue, 28 Jul 2020 18:58:58 -0400 Subject: [PATCH 67/75] [Canvas][tech-debt] Kill Recompose:Pure - Part 1 (#73303) Co-authored-by: Elastic Machine --- .../arg_add/{arg_add.js => arg_add.tsx} | 14 +- .../components/arg_add/{index.js => index.ts} | 6 +- .../arg_add_popover/arg_add_popover.tsx | 7 +- .../components/arg_add_popover/index.ts | 5 +- .../asset_manager.stories.storyshot | 539 ---------- .../public/components/color_dot/color_dot.tsx | 12 +- .../public/components/color_dot/index.ts | 7 +- .../color_manager/color_manager.tsx | 22 +- .../public/components/color_manager/index.ts | 7 +- .../color_palette/color_palette.tsx | 8 +- .../public/components/color_palette/index.ts | 6 +- .../components/color_picker/color_picker.tsx | 5 +- .../public/components/color_picker/index.ts | 7 +- .../color_picker_popover.tsx | 9 +- .../components/color_picker_popover/index.ts | 7 +- .../datatable/{datatable.js => datatable.tsx} | 33 +- .../datatable/{index.js => index.ts} | 5 +- .../font_picker.stories.storyshot | 0 .../{ => __stories__}/font_picker.stories.tsx | 4 +- .../components/font_picker/font_picker.tsx | 14 +- .../font_picker/{index.js => index.ts} | 6 +- .../canvas/public/components/loading/index.ts | 5 +- .../public/components/loading/loading.tsx | 6 +- .../public/components/shape_picker/index.ts | 6 +- .../components/shape_picker/shape_picker.tsx | 6 +- .../components/shape_picker_popover/index.tsx | 6 +- .../shape_picker_popover.tsx | 7 +- .../public/components/shape_preview/index.ts | 6 +- .../shape_preview/shape_preview.tsx | 4 +- .../text_style_picker.stories.storyshot | 975 ++++++++++++++++++ .../__stories__/text_style_picker.stories.tsx | 21 + .../{font_sizes.js => font_sizes.ts} | 0 .../text_style_picker/{index.js => index.ts} | 5 +- ..._style_picker.js => text_style_picker.tsx} | 160 +-- .../public/components/tooltip_icon/index.ts | 6 +- .../components/tooltip_icon/tooltip_icon.tsx | 4 +- 36 files changed, 1188 insertions(+), 752 deletions(-) rename x-pack/plugins/canvas/public/components/arg_add/{arg_add.js => arg_add.tsx} (71%) rename x-pack/plugins/canvas/public/components/arg_add/{index.js => index.ts} (66%) rename x-pack/plugins/canvas/public/components/datatable/{datatable.js => datatable.tsx} (78%) rename x-pack/plugins/canvas/public/components/datatable/{index.js => index.ts} (64%) rename x-pack/plugins/canvas/public/components/font_picker/{ => __stories__}/__snapshots__/font_picker.stories.storyshot (100%) rename x-pack/plugins/canvas/public/components/font_picker/{ => __stories__}/font_picker.stories.tsx (84%) rename x-pack/plugins/canvas/public/components/font_picker/{index.js => index.ts} (64%) create mode 100644 x-pack/plugins/canvas/public/components/text_style_picker/__stories__/__snapshots__/text_style_picker.stories.storyshot create mode 100644 x-pack/plugins/canvas/public/components/text_style_picker/__stories__/text_style_picker.stories.tsx rename x-pack/plugins/canvas/public/components/text_style_picker/{font_sizes.js => font_sizes.ts} (100%) rename x-pack/plugins/canvas/public/components/text_style_picker/{index.js => index.ts} (61%) rename x-pack/plugins/canvas/public/components/text_style_picker/{text_style_picker.js => text_style_picker.tsx} (54%) diff --git a/x-pack/plugins/canvas/public/components/arg_add/arg_add.js b/x-pack/plugins/canvas/public/components/arg_add/arg_add.tsx similarity index 71% rename from x-pack/plugins/canvas/public/components/arg_add/arg_add.js rename to x-pack/plugins/canvas/public/components/arg_add/arg_add.tsx index 2d6d7d1046fdd..e85a2915a82b1 100644 --- a/x-pack/plugins/canvas/public/components/arg_add/arg_add.js +++ b/x-pack/plugins/canvas/public/components/arg_add/arg_add.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC, ReactEventHandler } from 'react'; import PropTypes from 'prop-types'; import { EuiDescriptionList, @@ -12,7 +12,13 @@ import { EuiDescriptionListDescription, } from '@elastic/eui'; -export const ArgAdd = ({ onValueAdd, displayName, help }) => { +interface Props { + displayName: string; + help: string; + onValueAdd?: ReactEventHandler; +} + +export const ArgAdd: FC = ({ onValueAdd = () => {}, displayName, help }) => { return ( -
      -
      -
      - Manage workpad assets -
      -
      -
      -
      -
      - -
      - -
      -
      -
      -
      -
      -
      -
      -
      -
      -

      - Below are the image assets in this workpad. Any assets that are currently in use cannot be determined at this time. To reclaim space, delete assets. -

      -
      -
      -
      -
      -
      -
      -
      -
      - Asset thumbnail -
      -
      -
      -
      -

      - - airplane - -
      - - - ( - 1 - kb) - - -

      -
      -
      -
      -
      - - - -
      -
      - -
      - -
      -
      -
      -
      - -
      - -
      -
      -
      -
      - - - -
      -
      -
      -
      -
      -
      -
      -
      - Asset thumbnail -
      -
      -
      -
      -

      - - marker - -
      - - - ( - 1 - kb) - - -

      -
      -
      -
      -
      - - - -
      -
      - -
      - -
      -
      -
      -
      - -
      - -
      -
      -
      -
      - - - -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - -
      -
      -
      - 0% space used -
      -
      -
      - -
      -
      -
      -
      , -
      , -] -`; - exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` Array [
      = ({ value, children }) => { +export const ColorDot: FC = ({ value, children }) => { const tc = tinycolor(value); let style = {}; @@ -34,6 +34,6 @@ export const ColorDot: FunctionComponent = ({ value, children }) => { }; ColorDot.propTypes = { - value: PropTypes.string, children: PropTypes.node, + value: PropTypes.string, }; diff --git a/x-pack/plugins/canvas/public/components/color_dot/index.ts b/x-pack/plugins/canvas/public/components/color_dot/index.ts index aacfdf4e0cc74..72936f6133886 100644 --- a/x-pack/plugins/canvas/public/components/color_dot/index.ts +++ b/x-pack/plugins/canvas/public/components/color_dot/index.ts @@ -4,9 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ColorDot as Component } from './color_dot'; - -export { Props } from './color_dot'; -export const ColorDot = pure(Component); +export { ColorDot } from './color_dot'; diff --git a/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx b/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx index 8855bffc5e771..88bf93a3ca84a 100644 --- a/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx +++ b/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonIcon, EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; +import { EuiButtonIcon, EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import tinycolor from 'tinycolor2'; import { ColorDot } from '../color_dot/color_dot'; @@ -15,17 +15,17 @@ import { ComponentStrings } from '../../../i18n/components'; const { ColorManager: strings } = ComponentStrings; export interface Props { + /** + * Determines if the add/remove buttons are displayed. + * @default false + */ + hasButtons?: boolean; /** The function to call when the Add Color button is clicked. The button will be disabled if there is no handler. */ onAddColor?: (value: string) => void; /** The function to call when the value is changed */ onChange: (value: string) => void; /** The function to call when the Remove Color button is clicked. The button will be disabled if there is no handler. */ onRemoveColor?: (value: string) => void; - /** - * Determines if the add/remove buttons are displayed. - * @default false - */ - hasButtons?: boolean; /** * The value of the color manager. Only honors valid CSS values. * @default '' @@ -33,12 +33,12 @@ export interface Props { value?: string; } -export const ColorManager: FunctionComponent = ({ - value = '', +export const ColorManager: FC = ({ + hasButtons = false, onAddColor, - onRemoveColor, onChange, - hasButtons = false, + onRemoveColor, + value = '', }) => { const tc = tinycolor(value); const validColor = tc.isValid(); diff --git a/x-pack/plugins/canvas/public/components/color_manager/index.ts b/x-pack/plugins/canvas/public/components/color_manager/index.ts index d7f59b38a74c5..9958c17cf1941 100644 --- a/x-pack/plugins/canvas/public/components/color_manager/index.ts +++ b/x-pack/plugins/canvas/public/components/color_manager/index.ts @@ -4,9 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ColorManager as Component } from './color_manager'; - -export { Props } from './color_manager'; -export const ColorManager = pure(Component); +export { ColorManager, Props } from './color_manager'; diff --git a/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx b/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx index 09bc08f9ae541..d3b1936d4c365 100644 --- a/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx +++ b/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx @@ -4,15 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiIcon, EuiLink } from '@elastic/eui'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; +import { EuiIcon, EuiLink } from '@elastic/eui'; import tinycolor from 'tinycolor2'; import { readableColor } from '../../lib/readable_color'; import { ColorDot } from '../color_dot'; import { ItemGrid } from '../item_grid'; -export interface Props { +interface Props { /** * An array of hexadecimal color values. Non-hex will be ignored. * @default [] @@ -32,7 +32,7 @@ export interface Props { value?: string; } -export const ColorPalette: FunctionComponent = ({ +export const ColorPalette: FC = ({ colors = [], colorsPerRow = 6, onChange, diff --git a/x-pack/plugins/canvas/public/components/color_palette/index.ts b/x-pack/plugins/canvas/public/components/color_palette/index.ts index fa71bc8b3b9b0..2605868b94279 100644 --- a/x-pack/plugins/canvas/public/components/color_palette/index.ts +++ b/x-pack/plugins/canvas/public/components/color_palette/index.ts @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; -import { ColorPalette as Component } from './color_palette'; - -export { Props } from './color_palette'; -export const ColorPalette = pure(Component); +export { ColorPalette } from './color_palette'; diff --git a/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx b/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx index 2bf17301b7b38..8de3ddb3d03a4 100644 --- a/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx +++ b/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; import tinycolor from 'tinycolor2'; + import { ColorManager, Props as ColorManagerProps } from '../color_manager'; import { ColorPalette } from '../color_palette'; @@ -18,7 +19,7 @@ export interface Props extends ColorManagerProps { colors?: string[]; } -export const ColorPicker: FunctionComponent = ({ +export const ColorPicker: FC = ({ colors = [], hasButtons = false, onAddColor, diff --git a/x-pack/plugins/canvas/public/components/color_picker/index.ts b/x-pack/plugins/canvas/public/components/color_picker/index.ts index 88968d11a665c..35dd067ab6d39 100644 --- a/x-pack/plugins/canvas/public/components/color_picker/index.ts +++ b/x-pack/plugins/canvas/public/components/color_picker/index.ts @@ -4,9 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ColorPicker as Component } from './color_picker'; - -export { Props } from './color_picker'; -export const ColorPicker = pure(Component); +export { ColorPicker, Props } from './color_picker'; diff --git a/x-pack/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx b/x-pack/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx index 9e8a6e88b649b..143e1a7cee6ac 100644 --- a/x-pack/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx +++ b/x-pack/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx @@ -4,20 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiLink, PopoverAnchorPosition } from '@elastic/eui'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; +import { EuiLink, PopoverAnchorPosition } from '@elastic/eui'; import tinycolor from 'tinycolor2'; + import { ColorDot } from '../color_dot'; import { ColorPicker, Props as ColorPickerProps } from '../color_picker'; import { Popover } from '../popover'; export interface Props extends ColorPickerProps { - anchorPosition: PopoverAnchorPosition; + anchorPosition?: PopoverAnchorPosition; ariaLabel?: string; } -export const ColorPickerPopover: FunctionComponent = (props: Props) => { +export const ColorPickerPopover: FC = (props: Props) => { const { value, anchorPosition, ariaLabel, ...rest } = props; const button = (handleClick: React.MouseEventHandler) => ( { +const getIcon = (type: IconType) => { if (type === null) { return; } @@ -36,19 +39,31 @@ const getIcon = (type) => { return ; }; -const getColumnName = (col) => (typeof col === 'string' ? col : col.name); +const getColumnName = (col: DatatableColumn) => (typeof col === 'string' ? col : col.name); -const getColumnType = (col) => col.type || null; +const getColumnType = (col: DatatableColumn) => col.type || null; -const getFormattedValue = (val, type) => { +const getFormattedValue = (val: any, type: any) => { if (type === 'date') { return moment(val).format(); } return String(val); }; -export const Datatable = ({ datatable, perPage, paginate, showHeader }) => ( - +interface Props { + datatable: DatatableType; + paginate?: boolean; + perPage?: number; + showHeader?: boolean; +} + +export const Datatable: FC = ({ + datatable, + paginate = false, + perPage = 10, + showHeader = false, +}) => ( + {({ rows, setPage, pageNumber, totalPages }) => (
      @@ -91,7 +106,7 @@ export const Datatable = ({ datatable, perPage, paginate, showHeader }) => ( Datatable.propTypes = { datatable: PropTypes.object.isRequired, - perPage: PropTypes.number, paginate: PropTypes.bool, + perPage: PropTypes.number, showHeader: PropTypes.bool, }; diff --git a/x-pack/plugins/canvas/public/components/datatable/index.js b/x-pack/plugins/canvas/public/components/datatable/index.ts similarity index 64% rename from x-pack/plugins/canvas/public/components/datatable/index.js rename to x-pack/plugins/canvas/public/components/datatable/index.ts index c7837005368e5..e39b909c53460 100644 --- a/x-pack/plugins/canvas/public/components/datatable/index.js +++ b/x-pack/plugins/canvas/public/components/datatable/index.ts @@ -4,7 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; -import { Datatable as Component } from './datatable'; - -export const Datatable = pure(Component); +export { Datatable } from './datatable'; diff --git a/x-pack/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot b/x-pack/plugins/canvas/public/components/font_picker/__stories__/__snapshots__/font_picker.stories.storyshot similarity index 100% rename from x-pack/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot rename to x-pack/plugins/canvas/public/components/font_picker/__stories__/__snapshots__/font_picker.stories.storyshot diff --git a/x-pack/plugins/canvas/public/components/font_picker/font_picker.stories.tsx b/x-pack/plugins/canvas/public/components/font_picker/__stories__/font_picker.stories.tsx similarity index 84% rename from x-pack/plugins/canvas/public/components/font_picker/font_picker.stories.tsx rename to x-pack/plugins/canvas/public/components/font_picker/__stories__/font_picker.stories.tsx index 0ad1e01252002..34cb3d644cccb 100644 --- a/x-pack/plugins/canvas/public/components/font_picker/font_picker.stories.tsx +++ b/x-pack/plugins/canvas/public/components/font_picker/__stories__/font_picker.stories.tsx @@ -7,8 +7,8 @@ import { action } from '@storybook/addon-actions'; import { storiesOf } from '@storybook/react'; import React from 'react'; -import { americanTypewriter } from '../../../common/lib/fonts'; -import { FontPicker } from './font_picker'; +import { americanTypewriter } from '../../../../common/lib/fonts'; +import { FontPicker } from '../font_picker'; storiesOf('components/FontPicker', module) .add('default', () => ) diff --git a/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx b/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx index 556a3c5452160..2b75841e1b7a5 100644 --- a/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx +++ b/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiSuperSelect } from '@elastic/eui'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; +import { EuiSuperSelect } from '@elastic/eui'; import { fonts, FontValue } from '../../../common/lib/fonts'; interface DisplayedFont { - value: string; label: string; + value: string; } interface Props { @@ -19,9 +19,7 @@ interface Props { value?: FontValue; } -export const FontPicker: FunctionComponent = (props) => { - const { value, onSelect } = props; - +export const FontPicker: FC = ({ value, onSelect }) => { // While fonts are strongly-typed, we also support custom fonts someone might type in. // So let's cast the fonts and allow for additions. const displayedFonts: DisplayedFont[] = fonts; @@ -46,10 +44,10 @@ export const FontPicker: FunctionComponent = (props) => { }; FontPicker.propTypes = { - /** Initial value of the Font Picker. */ - value: PropTypes.string, /** Function to execute when a Font is selected. */ onSelect: PropTypes.func, + /** Initial value of the Font Picker. */ + value: PropTypes.string, }; FontPicker.displayName = 'FontPicker'; diff --git a/x-pack/plugins/canvas/public/components/font_picker/index.js b/x-pack/plugins/canvas/public/components/font_picker/index.ts similarity index 64% rename from x-pack/plugins/canvas/public/components/font_picker/index.js rename to x-pack/plugins/canvas/public/components/font_picker/index.ts index 5ccb7846b7a77..339021a7e5712 100644 --- a/x-pack/plugins/canvas/public/components/font_picker/index.js +++ b/x-pack/plugins/canvas/public/components/font_picker/index.ts @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { FontPicker as Component } from './font_picker'; - -export const FontPicker = pure(Component); +export { FontPicker } from './font_picker'; diff --git a/x-pack/plugins/canvas/public/components/loading/index.ts b/x-pack/plugins/canvas/public/components/loading/index.ts index 81fedf3287184..745639955dcba 100644 --- a/x-pack/plugins/canvas/public/components/loading/index.ts +++ b/x-pack/plugins/canvas/public/components/loading/index.ts @@ -4,7 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; -import { Loading as Component } from './loading'; - -export const Loading = pure(Component); +export { Loading } from './loading'; diff --git a/x-pack/plugins/canvas/public/components/loading/loading.tsx b/x-pack/plugins/canvas/public/components/loading/loading.tsx index 67db16d40d426..403be84295312 100644 --- a/x-pack/plugins/canvas/public/components/loading/loading.tsx +++ b/x-pack/plugins/canvas/public/components/loading/loading.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiIcon, EuiLoadingSpinner, isColorDark } from '@elastic/eui'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; +import { EuiIcon, EuiLoadingSpinner, isColorDark } from '@elastic/eui'; import { hexToRgb } from '../../../common/lib/hex_to_rgb'; interface Props { @@ -15,7 +15,7 @@ interface Props { text?: string; } -export const Loading: FunctionComponent = ({ +export const Loading: FC = ({ animated = false, text = '', backgroundColor = '#000000', diff --git a/x-pack/plugins/canvas/public/components/shape_picker/index.ts b/x-pack/plugins/canvas/public/components/shape_picker/index.ts index d3ed85831cbe2..3ec86e45af236 100644 --- a/x-pack/plugins/canvas/public/components/shape_picker/index.ts +++ b/x-pack/plugins/canvas/public/components/shape_picker/index.ts @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ShapePicker as Component } from './shape_picker'; - -export const ShapePicker = pure(Component); +export { ShapePicker } from './shape_picker'; diff --git a/x-pack/plugins/canvas/public/components/shape_picker/shape_picker.tsx b/x-pack/plugins/canvas/public/components/shape_picker/shape_picker.tsx index 56874fd3080f7..263654522c059 100644 --- a/x-pack/plugins/canvas/public/components/shape_picker/shape_picker.tsx +++ b/x-pack/plugins/canvas/public/components/shape_picker/shape_picker.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { EuiFlexGrid, EuiFlexItem, EuiLink } from '@elastic/eui'; import { ShapePreview } from '../shape_preview'; @@ -16,7 +16,7 @@ interface Props { onChange?: (key: string) => void; } -export const ShapePicker = ({ shapes, onChange = () => {} }: Props) => { +export const ShapePicker: FC = ({ shapes, onChange = () => {} }) => { return ( {Object.keys(shapes) @@ -33,6 +33,6 @@ export const ShapePicker = ({ shapes, onChange = () => {} }: Props) => { }; ShapePicker.propTypes = { - shapes: PropTypes.object.isRequired, onChange: PropTypes.func, + shapes: PropTypes.object.isRequired, }; diff --git a/x-pack/plugins/canvas/public/components/shape_picker_popover/index.tsx b/x-pack/plugins/canvas/public/components/shape_picker_popover/index.tsx index 1d4ae25a38fa2..06619c0626daf 100644 --- a/x-pack/plugins/canvas/public/components/shape_picker_popover/index.tsx +++ b/x-pack/plugins/canvas/public/components/shape_picker_popover/index.tsx @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ShapePickerPopover as Component } from './shape_picker_popover'; - -export const ShapePickerPopover = pure(Component); +export { ShapePickerPopover } from './shape_picker_popover'; diff --git a/x-pack/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx b/x-pack/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx index d42e08d2bc852..d61d9e47a3a78 100644 --- a/x-pack/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx +++ b/x-pack/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { EuiLink, EuiPanel } from '@elastic/eui'; import { Popover } from '../popover'; @@ -20,7 +20,7 @@ interface Props { ariaLabel?: string; } -export const ShapePickerPopover = ({ shapes, onChange, value, ariaLabel }: Props) => { +export const ShapePickerPopover: FC = ({ shapes, onChange, value, ariaLabel }) => { const button = (handleClick: React.MouseEventHandler) => ( @@ -37,7 +37,8 @@ export const ShapePickerPopover = ({ shapes, onChange, value, ariaLabel }: Props }; ShapePickerPopover.propTypes = { + ariaLabel: PropTypes.string, + onChange: PropTypes.func, shapes: PropTypes.object.isRequired, value: PropTypes.string, - onChange: PropTypes.func, }; diff --git a/x-pack/plugins/canvas/public/components/shape_preview/index.ts b/x-pack/plugins/canvas/public/components/shape_preview/index.ts index 4320a10d97a85..6027b1227a99a 100644 --- a/x-pack/plugins/canvas/public/components/shape_preview/index.ts +++ b/x-pack/plugins/canvas/public/components/shape_preview/index.ts @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ShapePreview as Component } from './shape_preview'; - -export const ShapePreview = pure(Component); +export { ShapePreview } from './shape_preview'; diff --git a/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx b/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx index 4f67945e9ce13..3ff18f3aa4bc4 100644 --- a/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx +++ b/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; interface Props { shape?: string; } -export const ShapePreview = ({ shape }: Props) => { +export const ShapePreview: FC = ({ shape }) => { if (!shape) { return
      ; } diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/__snapshots__/text_style_picker.stories.storyshot b/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/__snapshots__/text_style_picker.stories.storyshot new file mode 100644 index 0000000000000..ad236e701ceb0 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/__snapshots__/text_style_picker.stories.storyshot @@ -0,0 +1,975 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/TextStylePicker default 1`] = ` +
      +
      +
      +
      +
      +
      + +
      +
      + + Select an option: + , is selected + +
      +
      +
      +
      +
      +
      + +
      + +
      + + +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      +
      +
      +
      +
      +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      +
      +
      +
      +
      +
      +`; + +exports[`Storyshots components/TextStylePicker interactive 1`] = ` +
      +
      +
      +
      +
      +
      + +
      +
      + + Select an option: + , is selected + +
      +
      +
      +
      +
      +
      + +
      + +
      + + +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      +
      +
      +
      +
      +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      +
      +
      +
      +
      +
      +`; diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/text_style_picker.stories.tsx b/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/text_style_picker.stories.tsx new file mode 100644 index 0000000000000..b33a34fcd5e65 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/text_style_picker.stories.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { TextStylePicker } from '../text_style_picker'; + +const Interactive = () => { + const [props, setProps] = useState({}); + return ; +}; + +storiesOf('components/TextStylePicker', module) + .addDecorator((fn) =>
      {fn()}
      ) + .add('default', () => ) + .add('interactive', () => ); diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/font_sizes.js b/x-pack/plugins/canvas/public/components/text_style_picker/font_sizes.ts similarity index 100% rename from x-pack/plugins/canvas/public/components/text_style_picker/font_sizes.js rename to x-pack/plugins/canvas/public/components/text_style_picker/font_sizes.ts diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/index.js b/x-pack/plugins/canvas/public/components/text_style_picker/index.ts similarity index 61% rename from x-pack/plugins/canvas/public/components/text_style_picker/index.js rename to x-pack/plugins/canvas/public/components/text_style_picker/index.ts index 79bde95723682..16fb39b660a0c 100644 --- a/x-pack/plugins/canvas/public/components/text_style_picker/index.js +++ b/x-pack/plugins/canvas/public/components/text_style_picker/index.ts @@ -4,7 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; -import { TextStylePicker as Component } from './text_style_picker'; - -export const TextStylePicker = pure(Component); +export { TextStylePicker } from './text_style_picker'; diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.js b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx similarity index 54% rename from x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.js rename to x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx index 48d52abb03125..3dfc55919395d 100644 --- a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.js +++ b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC, useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { EuiFlexGroup, EuiFlexItem, EuiSelect, EuiSpacer, EuiButtonGroup } from '@elastic/eui'; +import { FontValue } from 'src/plugins/expressions'; import { ComponentStrings } from '../../../i18n'; import { FontPicker } from '../font_picker'; import { ColorPickerPopover } from '../color_picker_popover'; @@ -14,54 +15,75 @@ import { fontSizes } from './font_sizes'; const { TextStylePicker: strings } = ComponentStrings; -export const TextStylePicker = ({ - family, - size, - align, - color, - weight, - underline, - italic, - onChange, - colors, -}) => { - const alignmentButtons = [ - { - id: 'left', - label: strings.getAlignLeftOption(), - iconType: 'editorAlignLeft', - }, - { - id: 'center', - label: strings.getAlignCenterOption(), - iconType: 'editorAlignCenter', - }, - { - id: 'right', - label: strings.getAlignRightOption(), - iconType: 'editorAlignRight', - }, - ]; - - const styleButtons = [ - { - id: 'bold', - label: strings.getStyleBoldOption(), - iconType: 'editorBold', - }, - { - id: 'italic', - label: strings.getStyleItalicOption(), - iconType: 'editorItalic', - }, - { - id: 'underline', - label: strings.getStyleUnderlineOption(), - iconType: 'editorUnderline', - }, - ]; - - const stylesSelectedMap = { +interface BaseProps { + family?: FontValue; + size?: number; + align?: 'left' | 'center' | 'right'; + color?: string; + weight?: 'bold' | 'normal'; + underline?: boolean; + italic?: boolean; +} + +interface Props extends BaseProps { + colors?: string[]; + onChange: (props: BaseProps) => void; +} + +type StyleType = 'bold' | 'italic' | 'underline'; + +const alignmentButtons = [ + { + id: 'left', + label: strings.getAlignLeftOption(), + iconType: 'editorAlignLeft', + }, + { + id: 'center', + label: strings.getAlignCenterOption(), + iconType: 'editorAlignCenter', + }, + { + id: 'right', + label: strings.getAlignRightOption(), + iconType: 'editorAlignRight', + }, +]; + +const styleButtons = [ + { + id: 'bold', + label: strings.getStyleBoldOption(), + iconType: 'editorBold', + }, + { + id: 'italic', + label: strings.getStyleItalicOption(), + iconType: 'editorItalic', + }, + { + id: 'underline', + label: strings.getStyleUnderlineOption(), + iconType: 'editorUnderline', + }, +]; + +export const TextStylePicker: FC = (props) => { + const [style, setStyle] = useState(props); + + const { + align = 'left', + color, + colors, + family, + italic = false, + onChange, + size = 14, + underline = false, + weight = 'normal', + } = style; + + const stylesSelectedMap: Record = { ['bold']: weight === 'bold', ['italic']: Boolean(italic), ['underline']: Boolean(underline), @@ -72,31 +94,22 @@ export const TextStylePicker = ({ fontSizes.sort((a, b) => a - b); } - const doChange = (propName, value) => { - onChange({ - family, - size, - align, - color, - weight: weight || 'normal', - underline: underline || false, - italic: italic || false, - [propName]: value, - }); - }; + useEffect(() => onChange(style), [onChange, style]); - const onAlignmentChange = (optionId) => doChange('align', optionId); + const doChange = (propName: keyof Props, value: string | boolean | number) => { + setStyle({ ...style, [propName]: value }); + }; - const onStyleChange = (optionId) => { - let prop; + const onStyleChange = (optionId: string) => { + let prop: 'weight' | 'italic' | 'underline'; let value; if (optionId === 'bold') { prop = 'weight'; value = !stylesSelectedMap[optionId] ? 'bold' : 'normal'; } else { - prop = optionId; - value = !stylesSelectedMap[optionId]; + prop = optionId as 'italic' | 'underline'; + value = !stylesSelectedMap[prop]; } doChange(prop, value); @@ -106,14 +119,18 @@ export const TextStylePicker = ({
      - doChange('family', value)} /> + {family ? ( + doChange('family', value)} /> + ) : ( + doChange('family', value)} /> + )} doChange('size', Number(e.target.value))} - options={fontSizes.map((size) => ({ text: String(size), value: size }))} + options={fontSizes.map((fontSize) => ({ text: String(fontSize), value: fontSize }))} prepend="Size" /> @@ -147,7 +164,7 @@ export const TextStylePicker = ({ buttonSize="compressed" isIconOnly idSelected={align} - onChange={onAlignmentChange} + onChange={(optionId: string) => doChange('align', optionId)} className="canvasSidebar__buttonGroup" /> @@ -159,9 +176,9 @@ export const TextStylePicker = ({ TextStylePicker.propTypes = { family: PropTypes.string, size: PropTypes.number, - align: PropTypes.string, + align: PropTypes.oneOf(['left', 'center', 'right']), color: PropTypes.string, - weight: PropTypes.string, + weight: PropTypes.oneOf(['normal', 'bold']), underline: PropTypes.bool, italic: PropTypes.bool, onChange: PropTypes.func.isRequired, @@ -171,4 +188,5 @@ TextStylePicker.propTypes = { TextStylePicker.defaultProps = { align: 'left', size: 14, + weight: 'normal', }; diff --git a/x-pack/plugins/canvas/public/components/tooltip_icon/index.ts b/x-pack/plugins/canvas/public/components/tooltip_icon/index.ts index 6e71baa364785..55c2f7090629c 100644 --- a/x-pack/plugins/canvas/public/components/tooltip_icon/index.ts +++ b/x-pack/plugins/canvas/public/components/tooltip_icon/index.ts @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; -import { TooltipIcon as Component } from './tooltip_icon'; -export { IconType } from './tooltip_icon'; - -export const TooltipIcon = pure(Component); +export { TooltipIcon, IconType } from './tooltip_icon'; diff --git a/x-pack/plugins/canvas/public/components/tooltip_icon/tooltip_icon.tsx b/x-pack/plugins/canvas/public/components/tooltip_icon/tooltip_icon.tsx index 78c2b0ec53c9f..d91bb4bc9add9 100644 --- a/x-pack/plugins/canvas/public/components/tooltip_icon/tooltip_icon.tsx +++ b/x-pack/plugins/canvas/public/components/tooltip_icon/tooltip_icon.tsx @@ -5,7 +5,7 @@ */ /* eslint react/forbid-elements: 0 */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { EuiIconTip, PropsOf } from '@elastic/eui'; @@ -21,7 +21,7 @@ interface Props extends Omit { icon: IconType; } -export const TooltipIcon = ({ icon = IconType.info, ...rest }: Props) => { +export const TooltipIcon: FC = ({ icon = IconType.info, ...rest }) => { const icons = { [IconType.error]: { type: 'alert', color: 'danger' }, [IconType.warning]: { type: 'alert', color: 'warning' }, From b65ec4e07d0c47e86f930e9e72f5c3fd030aac46 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Tue, 28 Jul 2020 19:44:30 -0400 Subject: [PATCH 68/75] Get branch name from platform vs disk (#73534) https://github.com/elastic/kibana/blob/fa93a81ba67f5177024f1ab3b4ac68919a7824dc/src/core/server/plugins/types.ts#L280 & https://github.com/elastic/kibana/blob/27dbcb27964353619f686066c6ba8f25954a0881/src/core/server/config/types.ts#L25 Co-authored-by: Elastic Machine --- x-pack/plugins/ingest_manager/server/plugin.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index 5664a87501016..e7495df254a09 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -15,7 +15,6 @@ import { HttpServiceSetup, } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import packageJSON from '../../../../package.json'; import { LicensingPluginSetup, ILicense } from '../../licensing/server'; import { EncryptedSavedObjectsPluginStart, @@ -155,7 +154,7 @@ export class IngestManagerPlugin this.config$ = this.initializerContext.config.create(); this.isProductionMode = this.initializerContext.env.mode.prod; this.kibanaVersion = this.initializerContext.env.packageInfo.version; - this.kibanaBranch = packageJSON.branch; + this.kibanaBranch = this.initializerContext.env.packageInfo.branch; this.logger = this.initializerContext.logger.get(); } From b399fb03d1677bba0cd61cb5de4e0957fa1910fc Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Tue, 28 Jul 2020 17:47:41 -0600 Subject: [PATCH 69/75] [SIEM][Detection Engine][Lists] Adds the ability to change the timeout limits from 10 seconds for loads for imports (#73103) ## Summary By default the upload time limit for payloads is 10 seconds. This is really too short and we were getting internal QA bug reports that uploads are timing out on large value list importing. This PR adds the plumbing and unit tests to make the timeout configurable for routes. * Adds a single timeout option for routes and then normalizes that through Hapi for the socket, payload, and server timeouts. * Adds unit tests which test the various options * Adds integration tests which test the various options * Adds some NOTES about where there are odd behaviors/bugs within Hapi around validations and the timeouts * Adds a configurable 5 minute timeout to the large value lists route **Manual testing of the feature** You can manually test this by adding a configurable option to your chrome network throttle like so below where you throttle upload by some configurable amount. I chose to use 300 kbs/s upload Screen Shot 2020-07-23 at 11 26 01 AM And then run an import of large value lists using a large enough file that it will exceed 5 minutes: ![screen-shot-upload](https://user-images.githubusercontent.com/1151048/88318584-28ef6000-ccd8-11ea-90a1-8ca4aafabcb4.png) After 5 minutes you should see this message within your server side messages if you have configured your kibana.dev.yml to allow for these messages: ```ts server respons [10:52:31.377] [access:lists-all] POST /api/lists/items/_import?type=keyword 408 318292ms - 9.0B ``` Note that it should show you that it is trying to return a `408` after `318292ms` the timeout period. Sometimes you will get the 408 in the browser and sometimes the browser actually will not respect the 408 and continue staying in a pending state forever. This seems to be browser side issue and not a client/user land issue. If you get the browser message it will be this error toaster ![timeout-message](https://user-images.githubusercontent.com/1151048/88318760-74a20980-ccd8-11ea-9b7b-0d27f8eb6bce.png) ### Checklist - [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/master/packages/kbn-i18n/README.md) - [x] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- ...a-plugin-core-server.routeconfigoptions.md | 1 + ...-core-server.routeconfigoptions.timeout.md | 13 ++ src/core/server/http/http_server.test.ts | 127 ++++++++++++++++++ src/core/server/http/http_server.ts | 22 ++- .../http/integration_tests/router.test.ts | 124 +++++++++++++++++ src/core/server/http/router/request.ts | 2 + src/core/server/http/router/route.ts | 6 + src/core/server/server.api.md | 1 + x-pack/plugins/lists/common/constants.mock.ts | 4 +- x-pack/plugins/lists/server/config.mock.ts | 2 + x-pack/plugins/lists/server/config.test.ts | 22 +++ x-pack/plugins/lists/server/config.ts | 10 ++ .../server/routes/import_list_item_route.ts | 1 + .../server/services/lists/list_client.mock.ts | 2 + 14 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md diff --git a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md index 5a6103dfc57d4..fee6124f8d866 100644 --- a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md @@ -19,5 +19,6 @@ export interface RouteConfigOptions | [authRequired](./kibana-plugin-core-server.routeconfigoptions.authrequired.md) | boolean | 'optional' | Defines authentication mode for a route: - true. A user has to have valid credentials to access a resource - false. A user can access a resource without any credentials. - 'optional'. A user can access a resource if has valid credentials or no credentials at all. Can be useful when we grant access to a resource but want to identify a user if possible.Defaults to true if an auth mechanism is registered. | | [body](./kibana-plugin-core-server.routeconfigoptions.body.md) | Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody | Additional body options [RouteConfigOptionsBody](./kibana-plugin-core-server.routeconfigoptionsbody.md). | | [tags](./kibana-plugin-core-server.routeconfigoptions.tags.md) | readonly string[] | Additional metadata tag strings to attach to the route. | +| [timeout](./kibana-plugin-core-server.routeconfigoptions.timeout.md) | number | Timeouts for processing durations. Response timeout is in milliseconds. Default value: 2 minutes | | [xsrfRequired](./kibana-plugin-core-server.routeconfigoptions.xsrfrequired.md) | Method extends 'get' ? never : boolean | Defines xsrf protection requirements for a route: - true. Requires an incoming POST/PUT/DELETE request to contain kbn-xsrf header. - false. Disables xsrf protection.Set to true by default | diff --git a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md new file mode 100644 index 0000000000000..479fcf883ec4d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [RouteConfigOptions](./kibana-plugin-core-server.routeconfigoptions.md) > [timeout](./kibana-plugin-core-server.routeconfigoptions.timeout.md) + +## RouteConfigOptions.timeout property + +Timeouts for processing durations. Response timeout is in milliseconds. Default value: 2 minutes + +Signature: + +```typescript +timeout?: number; +``` diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 601eba835a54e..007d75a69b955 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -992,6 +992,133 @@ describe('body options', () => { }); }); +describe('timeout options', () => { + test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a POST', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.post( + { + path: '/', + validate: false, + options: { timeout: 300000 }, + }, + (context, req, res) => { + try { + return res.ok({ body: { timeout: req.route.options.timeout } }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + await server.start(); + await supertest(innerServer.listener).post('/').send({ test: 1 }).expect(200, { + timeout: 300000, + }); + }); + + test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a GET', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.get( + { + path: '/', + validate: false, + options: { timeout: 300000 }, + }, + (context, req, res) => { + try { + return res.ok({ body: { timeout: req.route.options.timeout } }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + await server.start(); + await supertest(innerServer.listener).get('/').expect(200, { + timeout: 300000, + }); + }); + + test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a DELETE', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.delete( + { + path: '/', + validate: false, + options: { timeout: 300000 }, + }, + (context, req, res) => { + try { + return res.ok({ body: { timeout: req.route.options.timeout } }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + await server.start(); + await supertest(innerServer.listener).delete('/').expect(200, { + timeout: 300000, + }); + }); + + test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a PUT', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.put( + { + path: '/', + validate: false, + options: { timeout: 300000 }, + }, + (context, req, res) => { + try { + return res.ok({ body: { timeout: req.route.options.timeout } }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + await server.start(); + await supertest(innerServer.listener).put('/').expect(200, { + timeout: 300000, + }); + }); + + test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a PATCH', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.patch( + { + path: '/', + validate: false, + options: { timeout: 300000 }, + }, + (context, req, res) => { + try { + return res.ok({ body: { timeout: req.route.options.timeout } }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + await server.start(); + await supertest(innerServer.listener).patch('/').expect(200, { + timeout: 300000, + }); + }); +}); + test('should return a stream in the body', async () => { const { registerRouter, server: innerServer } = await server.setup(config); diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 9c16162d69334..4b70f58deba99 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -161,8 +161,10 @@ export class HttpServer { this.log.debug(`registering route handler for [${route.path}]`); // Hapi does not allow payload validation to be specified for 'head' or 'get' requests const validate = isSafeMethod(route.method) ? undefined : { payload: true }; - const { authRequired, tags, body = {} } = route.options; + const { authRequired, tags, body = {}, timeout } = route.options; const { accepts: allow, maxBytes, output, parse } = body; + // Hapi does not allow timeouts on payloads to be specified for 'head' or 'get' requests + const payloadTimeout = isSafeMethod(route.method) || timeout == null ? undefined : timeout; const kibanaRouteState: KibanaRouteState = { xsrfRequired: route.options.xsrfRequired ?? !isSafeMethod(route.method), @@ -181,9 +183,23 @@ export class HttpServer { // validation applied in ./http_tools#getServerOptions // (All NP routes are already required to specify their own validation in order to access the payload) validate, - payload: [allow, maxBytes, output, parse].some((v) => typeof v !== 'undefined') - ? { allow, maxBytes, output, parse } + payload: [allow, maxBytes, output, parse, payloadTimeout].some( + (v) => typeof v !== 'undefined' + ) + ? { + allow, + maxBytes, + output, + parse, + timeout: payloadTimeout, + } : undefined, + timeout: + timeout != null + ? { + socket: timeout + 1, // Hapi server requires the socket to be greater than payload settings so we add 1 millisecond + } + : undefined, }, }); } diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index bb36fefa96611..434e22e3cf6f5 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -302,6 +302,130 @@ describe('Options', () => { }); }); }); + + describe('timeout', () => { + it('should timeout if configured with a small timeout value for a POST', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.post( + { path: '/a', validate: false, options: { timeout: 1000 } }, + async (context, req, res) => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + return res.ok({}); + } + ); + router.post({ path: '/b', validate: false }, (context, req, res) => res.ok({})); + await server.start(); + expect(supertest(innerServer.listener).post('/a')).rejects.toThrow('socket hang up'); + await supertest(innerServer.listener).post('/b').expect(200, {}); + }); + + it('should timeout if configured with a small timeout value for a PUT', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.put( + { path: '/a', validate: false, options: { timeout: 1000 } }, + async (context, req, res) => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + return res.ok({}); + } + ); + router.put({ path: '/b', validate: false }, (context, req, res) => res.ok({})); + await server.start(); + + expect(supertest(innerServer.listener).put('/a')).rejects.toThrow('socket hang up'); + await supertest(innerServer.listener).put('/b').expect(200, {}); + }); + + it('should timeout if configured with a small timeout value for a DELETE', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.delete( + { path: '/a', validate: false, options: { timeout: 1000 } }, + async (context, req, res) => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + return res.ok({}); + } + ); + router.delete({ path: '/b', validate: false }, (context, req, res) => res.ok({})); + await server.start(); + expect(supertest(innerServer.listener).delete('/a')).rejects.toThrow('socket hang up'); + await supertest(innerServer.listener).delete('/b').expect(200, {}); + }); + + it('should timeout if configured with a small timeout value for a GET', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.get( + // Note: There is a bug within Hapi Server where it cannot set the payload timeout for a GET call but it also cannot configure a timeout less than the payload body + // so the least amount of possible time to configure the timeout is 10 seconds. + { path: '/a', validate: false, options: { timeout: 100000 } }, + async (context, req, res) => { + // Cause a wait of 20 seconds to cause the socket hangup + await new Promise((resolve) => setTimeout(resolve, 200000)); + return res.ok({}); + } + ); + router.get({ path: '/b', validate: false }, (context, req, res) => res.ok({})); + await server.start(); + + expect(supertest(innerServer.listener).get('/a')).rejects.toThrow('socket hang up'); + await supertest(innerServer.listener).get('/b').expect(200, {}); + }); + + it('should not timeout if configured with a 5 minute timeout value for a POST', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.post( + { path: '/a', validate: false, options: { timeout: 300000 } }, + async (context, req, res) => res.ok({}) + ); + await server.start(); + await supertest(innerServer.listener).post('/a').expect(200, {}); + }); + + it('should not timeout if configured with a 5 minute timeout value for a PUT', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.put( + { path: '/a', validate: false, options: { timeout: 300000 } }, + async (context, req, res) => res.ok({}) + ); + await server.start(); + + await supertest(innerServer.listener).put('/a').expect(200, {}); + }); + + it('should not timeout if configured with a 5 minute timeout value for a DELETE', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.delete( + { path: '/a', validate: false, options: { timeout: 300000 } }, + async (context, req, res) => res.ok({}) + ); + await server.start(); + await supertest(innerServer.listener).delete('/a').expect(200, {}); + }); + + it('should not timeout if configured with a 5 minute timeout value for a GET', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.get( + { path: '/a', validate: false, options: { timeout: 300000 } }, + async (context, req, res) => res.ok({}) + ); + await server.start(); + await supertest(innerServer.listener).get('/a').expect(200, {}); + }); + }); }); describe('Cache-Control', () => { diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index fefd75ad9710e..0e73431fe7c6d 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -197,12 +197,14 @@ export class KibanaRequest< private getRouteInfo(request: Request): KibanaRequestRoute { const method = request.method as Method; const { parse, maxBytes, allow, output } = request.route.settings.payload || {}; + const timeout = request.route.settings.timeout?.socket; const options = ({ authRequired: this.getAuthRequired(request), // some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8 xsrfRequired: (request.route.settings.app as KibanaRouteState)?.xsrfRequired ?? true, tags: request.route.settings.tags || [], + timeout: typeof timeout === 'number' ? timeout - 1 : undefined, // We are forced to have the timeout be 1 millisecond greater than the server and payload so we subtract one here to give the user consist settings body: isSafeMethod(method) ? undefined : { diff --git a/src/core/server/http/router/route.ts b/src/core/server/http/router/route.ts index 9789d266587af..676c494bec522 100644 --- a/src/core/server/http/router/route.ts +++ b/src/core/server/http/router/route.ts @@ -144,6 +144,12 @@ export interface RouteConfigOptions { * Additional body options {@link RouteConfigOptionsBody}. */ body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; + + /** + * Timeouts for processing durations. Response timeout is in milliseconds. + * Default value: 2 minutes + */ + timeout?: number; } /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index bb4f2f30ac18f..c94151f8cee17 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1859,6 +1859,7 @@ export interface RouteConfigOptions { authRequired?: boolean | 'optional'; body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; tags?: readonly string[]; + timeout?: number; xsrfRequired?: Method extends 'get' ? never : boolean; } diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts index 22706890e2020..b7609b5a3602a 100644 --- a/x-pack/plugins/lists/common/constants.mock.ts +++ b/x-pack/plugins/lists/common/constants.mock.ts @@ -3,8 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EntriesArray } from './schemas/types'; +import moment from 'moment'; +import { EntriesArray } from './schemas/types'; export const DATE_NOW = '2020-04-20T15:25:31.830Z'; export const OLD_DATE_RELATIVE_TO_DATE_NOW = '2020-04-19T15:25:31.830Z'; export const USER = 'some user'; @@ -64,3 +65,4 @@ export const CURSOR = 'c29tZXN0cmluZ2ZvcnlvdQ=='; export const _VERSION = 'WzI5NywxXQ=='; export const VERSION = 1; export const IMMUTABLE = false; +export const IMPORT_TIMEOUT = moment.duration(5, 'minutes'); diff --git a/x-pack/plugins/lists/server/config.mock.ts b/x-pack/plugins/lists/server/config.mock.ts index 3cf5040c73675..b272f18c4e809 100644 --- a/x-pack/plugins/lists/server/config.mock.ts +++ b/x-pack/plugins/lists/server/config.mock.ts @@ -6,6 +6,7 @@ import { IMPORT_BUFFER_SIZE, + IMPORT_TIMEOUT, LIST_INDEX, LIST_ITEM_INDEX, MAX_IMPORT_PAYLOAD_BYTES, @@ -21,6 +22,7 @@ export const getConfigMock = (): Partial => ({ export const getConfigMockDecoded = (): ConfigType => ({ enabled: true, importBufferSize: IMPORT_BUFFER_SIZE, + importTimeout: IMPORT_TIMEOUT, listIndex: LIST_INDEX, listItemIndex: LIST_ITEM_INDEX, maxImportPayloadBytes: MAX_IMPORT_PAYLOAD_BYTES, diff --git a/x-pack/plugins/lists/server/config.test.ts b/x-pack/plugins/lists/server/config.test.ts index 60501322dcfa2..40b04edd4c007 100644 --- a/x-pack/plugins/lists/server/config.test.ts +++ b/x-pack/plugins/lists/server/config.test.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; + import { ConfigSchema, ConfigType } from './config'; import { getConfigMock, getConfigMockDecoded } from './config.mock'; @@ -61,4 +63,24 @@ describe('config_schema', () => { '[importBufferSize]: Value must be equal to or greater than [1].' ); }); + + test('it throws if the "importTimeout" value is less than 2 minutes', () => { + const mock: ConfigType = { + ...getConfigMockDecoded(), + importTimeout: moment.duration(2, 'minutes').subtract(1, 'second'), + }; + expect(() => ConfigSchema.validate(mock)).toThrow( + '[importTimeout]: duration cannot be less than 2 minutes' + ); + }); + + test('it throws if the "importTimeout" value is greater than 1 hour', () => { + const mock: ConfigType = { + ...getConfigMockDecoded(), + importTimeout: moment.duration(1, 'hour').add(1, 'second'), + }; + expect(() => ConfigSchema.validate(mock)).toThrow( + '[importTimeout]: duration cannot be greater than 30 minutes' + ); + }); }); diff --git a/x-pack/plugins/lists/server/config.ts b/x-pack/plugins/lists/server/config.ts index 394f85ecfb642..6e36d135abfcc 100644 --- a/x-pack/plugins/lists/server/config.ts +++ b/x-pack/plugins/lists/server/config.ts @@ -9,6 +9,16 @@ import { TypeOf, schema } from '@kbn/config-schema'; export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), importBufferSize: schema.number({ defaultValue: 1000, min: 1 }), + importTimeout: schema.duration({ + defaultValue: '5m', + validate: (value) => { + if (value.asMinutes() < 2) { + throw new Error('duration cannot be less than 2 minutes'); + } else if (value.asMinutes() > 30) { + throw new Error('duration cannot be greater than 30 minutes'); + } + }, + }), listIndex: schema.string({ defaultValue: '.lists' }), listItemIndex: schema.string({ defaultValue: '.items' }), maxImportPayloadBytes: schema.number({ defaultValue: 9000000, min: 1 }), diff --git a/x-pack/plugins/lists/server/routes/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/import_list_item_route.ts index 1003a0c52a794..e162e7829e456 100644 --- a/x-pack/plugins/lists/server/routes/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/import_list_item_route.ts @@ -27,6 +27,7 @@ export const importListItemRoute = (router: IRouter, config: ConfigType): void = parse: false, }, tags: ['access:lists-all'], + timeout: config.importTimeout.asMilliseconds(), }, path: `${LIST_ITEM_URL}/_import`, validate: { diff --git a/x-pack/plugins/lists/server/services/lists/list_client.mock.ts b/x-pack/plugins/lists/server/services/lists/list_client.mock.ts index e5036d561ddc6..a2959a024f292 100644 --- a/x-pack/plugins/lists/server/services/lists/list_client.mock.ts +++ b/x-pack/plugins/lists/server/services/lists/list_client.mock.ts @@ -11,6 +11,7 @@ import { getListResponseMock } from '../../../common/schemas/response/list_schem import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { IMPORT_BUFFER_SIZE, + IMPORT_TIMEOUT, LIST_INDEX, LIST_ITEM_INDEX, MAX_IMPORT_PAYLOAD_BYTES, @@ -65,6 +66,7 @@ export const getListClientMock = (): ListClient => { config: { enabled: true, importBufferSize: IMPORT_BUFFER_SIZE, + importTimeout: IMPORT_TIMEOUT, listIndex: LIST_INDEX, listItemIndex: LIST_ITEM_INDEX, maxImportPayloadBytes: MAX_IMPORT_PAYLOAD_BYTES, From 89a392bd7cc1e6010c7518abcf948232ce855b79 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Tue, 28 Jul 2020 19:55:13 -0400 Subject: [PATCH 70/75] [Ingest Manager] API sends 404 when package config id is missing (#73212) * Add test to confirm missing config responds w/ 404 Currently failing with a 500 as in https://github.com/elastic/kibana/issues/66388 * Use after() to remove items added by test. The test initally failed with a 500 when the `after` was added. Debugging narrowed it down to a missing default config. getDefaultAgentConfigId errors if there isn't a default config. The config is added by `setupIngestManager` which _was_ always called during plugin#start but is no longer. We could add the setup call to the test/suite, but instead I changed AgentConfigService.delete to use ensureDefaultAgentConfig instead of getDefaultAgentConfigId. ensureDefaultAgentConfig adds one if it's missing. The check in delete is to make sure we don't delete the default config. We can still do that and now we add a config if it wasn't already there (which seems like A Good Thing) * Fix package config path in OpenApi spec * Return 404 if package config id is invalid/missing * Change test for error displayed text Co-authored-by: Elastic Machine --- .../common/openapi/spec_oas3.json | 4 +- .../server/routes/package_config/handlers.ts | 23 +++-- .../server/services/agent_config.ts | 2 +- .../apis/index.js | 1 + .../apis/package_config/get.ts | 89 +++++++++++++++++++ .../apps/endpoint/policy_details.ts | 2 +- 6 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 x-pack/test/ingest_manager_api_integration/apis/package_config/get.ts diff --git a/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json b/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json index e16edac5ddb7a..cfae2c450c824 100644 --- a/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json +++ b/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json @@ -922,7 +922,7 @@ }, "parameters": [] }, - "/packageConfigs": { + "/package_configs": { "get": { "summary": "PackageConfigs - List", "tags": [], @@ -1237,7 +1237,7 @@ ] } }, - "/packageConfigs/{packageConfigId}": { + "/package_configs/{packageConfigId}": { "get": { "summary": "PackageConfigs - Info", "tags": [], diff --git a/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts index 6b0c2fe9c2ff7..d2820cdbeb6c8 100644 --- a/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts @@ -5,7 +5,7 @@ */ import { TypeOf } from '@kbn/config-schema'; import Boom from 'boom'; -import { RequestHandler } from 'src/core/server'; +import { RequestHandler, SavedObjectsErrorHelpers } from '../../../../../../src/core/server'; import { appContextService, packageConfigService } from '../../services'; import { getPackageInfo } from '../../services/epm/packages'; import { @@ -49,8 +49,12 @@ export const getOnePackageConfigHandler: RequestHandler> = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const { packageConfigId } = request.params; + const notFoundResponse = () => + response.notFound({ body: { message: `Package config ${packageConfigId} not found` } }); + try { - const packageConfig = await packageConfigService.get(soClient, request.params.packageConfigId); + const packageConfig = await packageConfigService.get(soClient, packageConfigId); if (packageConfig) { return response.ok({ body: { @@ -58,17 +62,18 @@ export const getOnePackageConfigHandler: RequestHandler {} here + // because `this` has to point to the Mocha context + // see https://mochajs.org/#arrow-functions + + describe('Package Config - get by id', async function () { + skipIfNoDockerRegistry(providerContext); + let agentConfigId: string; + let packageConfigId: string; + + before(async function () { + if (!server.enabled) { + return; + } + const { body: agentConfigResponse } = await supertest + .post(`/api/ingest_manager/agent_configs`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Test config', + namespace: 'default', + }); + agentConfigId = agentConfigResponse.item.id; + + const { body: packageConfigResponse } = await supertest + .post(`/api/ingest_manager/package_configs`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'filetest-1', + description: '', + namespace: 'default', + config_id: agentConfigId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }); + packageConfigId = packageConfigResponse.item.id; + }); + + after(async function () { + if (!server.enabled) { + return; + } + + await supertest + .post(`/api/ingest_manager/agent_configs/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ agentConfigId }) + .expect(200); + + await supertest + .post(`/api/ingest_manager/package_configs/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ packageConfigIds: [packageConfigId] }) + .expect(200); + }); + + it('should succeed with a valid id', async function () { + const { body: apiResponse } = await supertest + .get(`/api/ingest_manager/package_configs/${packageConfigId}`) + .expect(200); + + expect(apiResponse.success).to.be(true); + }); + + it('should return a 404 with an invalid id', async function () { + await supertest.get(`/api/ingest_manager/package_configs/IS_NOT_PRESENT`).expect(404); + }); + }); +} diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index cf76f297d83be..8b197a6c69a30 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -27,7 +27,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.policy.navigateToPolicyDetails('invalid-id'); await testSubjects.existOrFail('policyDetailsIdNotFoundMessage'); expect(await testSubjects.getVisibleText('policyDetailsIdNotFoundMessage')).to.equal( - 'Saved object [ingest-package-configs/invalid-id] not found' + 'Package config invalid-id not found' ); }); }); From 14b2cbb15575be3d488ce36dac25244629b180a6 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Tue, 28 Jul 2020 19:58:26 -0400 Subject: [PATCH 71/75] [Security Solution][Resolver] Handle iso time strings (#73551) Co-authored-by: Elastic Machine --- .../public/resolver/lib/date.test.ts | 56 ++++++++++++++----- .../public/resolver/lib/date.ts | 29 ++++++++-- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/lib/date.test.ts b/x-pack/plugins/security_solution/public/resolver/lib/date.test.ts index 7a48245fcfc41..5555578e44f7b 100644 --- a/x-pack/plugins/security_solution/public/resolver/lib/date.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/lib/date.test.ts @@ -3,38 +3,64 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { getFriendlyElapsedTime } from './date'; +import { getFriendlyElapsedTime, getUnixTime } from './date'; describe('date', () => { - describe('getFriendlyElapsedTime', () => { - const second = 1000; - const minute = second * 60; - const hour = minute * 60; - const day = hour * 24; - const week = day * 7; - const month = day * 30; - const year = day * 365; + const second = 1000; + const minute = second * 60; + const hour = minute * 60; + const day = hour * 24; + const week = day * 7; + const month = day * 30; + const year = day * 365; - const initialTime = new Date('6/1/2020').getTime(); + describe('getUnixTime', () => { + const unixTime = new Date('6/1/2020').getTime(); + const unixStringTime = String(unixTime); + const isoTime = new Date('6/1/2020').toISOString(); + const notATime = 'imLate'; + + it('should return the time if already a unix timestamp', () => { + expect(getUnixTime(unixTime)).toEqual(unixTime); + }); + + it('should properly convert a unix timestamp string to a number', () => { + expect(getUnixTime(unixStringTime)).toEqual(unixTime); + }); + + it('should properly convert an ISO string to a unix timestamp', () => { + expect(getUnixTime(isoTime)).toEqual(unixTime); + }); - const oneMillisecond = new Date(initialTime + 1).getTime(); + it('should return NaN if an invalid time is provided', () => { + expect(getUnixTime(notATime)).toBeNaN(); + }); + }); + + describe('getFriendlyElapsedTime', () => { + const initialTime = new Date('6/1/2020').getTime(); + const oneMillisecond = new Date(initialTime + 1).toISOString(); const oneSecond = new Date(initialTime + 1 * second).getTime(); const oneMinute = new Date(initialTime + 1 * minute).getTime(); - const oneHour = new Date(initialTime + 1 * hour).getTime(); + const oneHour = new Date(initialTime + 1 * hour).toISOString(); const oneDay = new Date(initialTime + 1 * day).getTime(); - const oneWeek = new Date(initialTime + 1 * week).getTime(); + const oneWeek = `${new Date(initialTime + 1 * week).getTime()}`; const oneMonth = new Date(initialTime + 1 * month).getTime(); const oneYear = new Date(initialTime + 1 * year).getTime(); const almostASecond = new Date(initialTime + 999).getTime(); - const almostAMinute = new Date(initialTime + 59.9 * second).getTime(); + const almostAMinute = new Date(initialTime + 59.9 * second).toISOString(); const almostAnHour = new Date(initialTime + 59.9 * minute).getTime(); const almostADay = new Date(initialTime + 23.9 * hour).getTime(); - const almostAWeek = new Date(initialTime + 6.9 * day).getTime(); + const almostAWeek = new Date(initialTime + 6.9 * day).toISOString(); const almostAMonth = new Date(initialTime + 3.9 * week).getTime(); const almostAYear = new Date(initialTime + 11.9 * month).getTime(); const threeYears = new Date(initialTime + 3 * year).getTime(); + it('should return null if invalid times are given', () => { + expect(getFriendlyElapsedTime(initialTime, 'ImTimeless')).toEqual(null); + }); + it('should return the correct singular relative time', () => { expect(getFriendlyElapsedTime(initialTime, initialTime)).toEqual({ duration: '<1', diff --git a/x-pack/plugins/security_solution/public/resolver/lib/date.ts b/x-pack/plugins/security_solution/public/resolver/lib/date.ts index a5e07e6a02a88..3cd0c910f46f3 100644 --- a/x-pack/plugins/security_solution/public/resolver/lib/date.ts +++ b/x-pack/plugins/security_solution/public/resolver/lib/date.ts @@ -6,6 +6,26 @@ import { DurationDetails, DurationTypes } from '../types'; +/** + * Given a time, it will convert it to a unix timestamp if not one already. If it is unable to do so, it will return NaN + */ +export const getUnixTime = (time: number | string): number | typeof NaN => { + if (!time) { + return NaN; + } + if (typeof time === 'number') { + return time; + } + // If it's a date string just get the time in MS + let unixTime = Date.parse(time); + if (Number.isNaN(unixTime)) { + // If not an ISO date string, last check will be if it's a unix timestamp string + unixTime = parseInt(time, 10); + } + + return unixTime; +}; + /* * Given two unix timestamps, it will return an object containing the time difference and properly pluralized friendly version of the time difference. * i.e. a time difference of 1000ms will yield => { duration: 1, durationType: 'second' } and 10000ms will yield => { duration: 10, durationType: 'seconds' } @@ -15,12 +35,13 @@ export const getFriendlyElapsedTime = ( from: number | string, to: number | string ): DurationDetails | null => { - const startTime = typeof from === 'number' ? from : parseInt(from, 10); - const endTime = typeof to === 'number' ? to : parseInt(to, 10); - const elapsedTimeInMs = endTime - startTime; - if (Number.isNaN(elapsedTimeInMs)) { + const startTime = getUnixTime(from); + const endTime = getUnixTime(to); + + if (Number.isNaN(startTime) || Number.isNaN(endTime)) { return null; } + const elapsedTimeInMs = endTime - startTime; const second = 1000; const minute = second * 60; From 17ec168c287733253345854eff2e75f9b151a932 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Tue, 28 Jul 2020 19:58:58 -0400 Subject: [PATCH 72/75] [Security Solution][Resolver] Undo origin panel update (#73501) Co-authored-by: Elastic Machine --- .../public/resolver/view/map.tsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/view/map.tsx b/x-pack/plugins/security_solution/public/resolver/view/map.tsx index 19c403f1257be..0ca71c5bf60ce 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/map.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/map.tsx @@ -8,7 +8,7 @@ /* eslint-disable react/display-name */ -import React, { useContext, useEffect } from 'react'; +import React, { useContext } from 'react'; import { useSelector } from 'react-redux'; import { useEffectOnce } from 'react-use'; import { EuiLoadingSpinner } from '@elastic/eui'; @@ -68,25 +68,12 @@ export const ResolverMap = React.memo(function ({ const hasError = useSelector(selectors.hasError); const activeDescendantId = useSelector(selectors.ariaActiveDescendant); const { colorMap } = useResolverTheme(); - const { - cleanUpQueryParams, - queryParams: { crumbId }, - pushToQueryParams, - } = useResolverQueryParams(); + const { cleanUpQueryParams } = useResolverQueryParams(); useEffectOnce(() => { return () => cleanUpQueryParams(); }); - useEffect(() => { - // When you refresh the page after selecting a process in the table view (not the timeline view) - // The old crumbId still exists in the query string even though a resolver is no longer visible - // This just makes sure the activeDescendant and crumbId are in sync on load for that view as well as the timeline - if (activeDescendantId && crumbId !== activeDescendantId) { - pushToQueryParams({ crumbId: activeDescendantId, crumbEvent: '' }); - } - }, [crumbId, activeDescendantId, pushToQueryParams]); - return ( {isLoading ? ( From 774d3591c083e14bdd56339f2a93eb27e1d531f5 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Wed, 29 Jul 2020 01:03:45 +0100 Subject: [PATCH 73/75] [Security Solution] Update security overview splash (#73050) ## Summary https://github.com/elastic/endpoint-app-team/issues/591 How to verify: 1. go to: x-pack/test/security_solution_cypress/runner.ts 2. comment line 20 (await esArchiver.load('auditbeat');) 3. in line 25 change cypress:run for cypress:open 4. then in our directory run yarn cypress:run-as-ci when the cypress is open, 5. you can access the Kibana instance in port 5620 with username elastic and password changeme Screenshot 2020-07-23 at 14 48 34 ### 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/master/packages/kbn-i18n/README.md) - [ ] ~[Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~ - [ ] ~[Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios~ - [ ] ~This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~ - [ ] ~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)~ - [ ] ~This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers)~ ### For maintainers - [ ] ~This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~ --- .../public/doc_links/doc_links_service.ts | 2 +- .../pages/saved_object_no_permissions.tsx | 18 +- .../__snapshots__/index.test.tsx.snap | 24 ++- .../components/empty_page/index.test.tsx | 14 +- .../common/components/empty_page/index.tsx | 157 +++++++++++------- .../public/common/translations.ts | 39 ++++- .../detection_engine.test.tsx | 3 + .../detection_engine_no_signal_index.tsx | 19 ++- .../detection_engine_user_unauthenticated.tsx | 19 ++- .../pages/detection_engine/translations.ts | 4 +- .../components/overview_empty/index.test.tsx | 83 +++++++++ .../components/overview_empty/index.tsx | 53 ++++-- .../public/overview/pages/overview.test.tsx | 4 +- .../public/overview/pages/overview.tsx | 2 +- .../translations/translations/ja-JP.json | 4 +- .../translations/translations/zh-CN.json | 4 +- 16 files changed, 331 insertions(+), 118 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 70b25cb78787a..eb54983d0be13 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -112,7 +112,7 @@ export class DocLinksService { kibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index.html`, siem: { guide: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, - gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/install-siem.html`, + gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, }, query: { luceneQuerySyntax: `${ELASTICSEARCH_DOCS}query-dsl-query-string-query.html#query-string-syntax`, diff --git a/x-pack/plugins/security_solution/public/cases/pages/saved_object_no_permissions.tsx b/x-pack/plugins/security_solution/public/cases/pages/saved_object_no_permissions.tsx index 7129aa04bdf69..c61ff6d18caab 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/saved_object_no_permissions.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/saved_object_no_permissions.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EmptyPage } from '../../common/components/empty_page'; import * as i18n from './translations'; @@ -12,13 +12,21 @@ import { useKibana } from '../../common/lib/kibana'; export const CaseSavedObjectNoPermissions = React.memo(() => { const docLinks = useKibana().services.docLinks; + const actions = useMemo( + () => ({ + savedObject: { + icon: 'documents', + label: i18n.GO_TO_DOCUMENTATION, + url: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/security/${docLinks.DOC_LINK_VERSION}s`, + target: '_blank', + }, + }), + [docLinks] + ); return ( - - Do Something - + + Do Something + + } + title={false} + /> } diff --git a/x-pack/plugins/security_solution/public/common/components/empty_page/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/empty_page/index.test.tsx index 6a14c12cee0f8..8e025faefeabe 100644 --- a/x-pack/plugins/security_solution/public/common/components/empty_page/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/empty_page/index.test.tsx @@ -10,12 +10,12 @@ import React from 'react'; import { EmptyPage } from './index'; test('renders correctly', () => { - const EmptyComponent = shallow( - - ); + const actions = { + actions: { + label: 'Do Something', + url: 'my/url/from/nowwhere', + }, + }; + const EmptyComponent = shallow(); expect(EmptyComponent).toMatchSnapshot(); }); diff --git a/x-pack/plugins/security_solution/public/common/components/empty_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/empty_page/index.tsx index f6d6752729b6d..89f4b125e930c 100644 --- a/x-pack/plugins/security_solution/public/common/components/empty_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/empty_page/index.tsx @@ -4,84 +4,123 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, IconType } from '@elastic/eui'; -import React, { MouseEventHandler, ReactNode } from 'react'; +import { + EuiButton, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + IconType, + EuiCard, +} from '@elastic/eui'; +import React, { MouseEventHandler, ReactNode, useMemo } from 'react'; import styled from 'styled-components'; const EmptyPrompt = styled(EuiEmptyPrompt)` align-self: center; /* Corrects horizontal centering in IE11 */ + max-width: 60em; `; EmptyPrompt.displayName = 'EmptyPrompt'; +interface EmptyPageActions { + icon?: IconType; + label: string; + target?: string; + url: string; + descriptionTitle?: string; + description?: string; + fill?: boolean; + onClick?: MouseEventHandler; +} + +export type EmptyPageActionsProps = Record; + interface EmptyPageProps { - actionPrimaryIcon?: IconType; - actionPrimaryLabel: string; - actionPrimaryTarget?: string; - actionPrimaryUrl: string; - actionPrimaryFill?: boolean; - actionSecondaryIcon?: IconType; - actionSecondaryLabel?: string; - actionSecondaryTarget?: string; - actionSecondaryUrl?: string; - actionSecondaryOnClick?: MouseEventHandler; + actions: EmptyPageActionsProps; 'data-test-subj'?: string; message?: ReactNode; title: string; } -export const EmptyPage = React.memo( - ({ - actionPrimaryIcon, - actionPrimaryLabel, - actionPrimaryTarget, - actionPrimaryUrl, - actionPrimaryFill = true, - actionSecondaryIcon, - actionSecondaryLabel, - actionSecondaryTarget, - actionSecondaryUrl, - actionSecondaryOnClick, - message, - title, - ...rest - }) => ( +const EmptyPageComponent = React.memo(({ actions, message, title, ...rest }) => { + const titles = Object.keys(actions); + const maxItemWidth = 283; + const renderActions = useMemo( + () => + Object.values(actions) + .filter((a) => a.label && a.url) + .map( + ( + { + icon, + label, + target, + url, + descriptionTitle = false, + description = false, + onClick, + fill = true, + }, + idx + ) => + descriptionTitle != null || description != null ? ( + + + {label} + + } + /> + + ) : ( + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + {label} + + + ) + ), + [actions, titles] + ); + + return ( {title}} body={message &&

      {message}

      } - actions={ - - - - {actionPrimaryLabel} - - - - {actionSecondaryLabel && actionSecondaryUrl && ( - - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - - {actionSecondaryLabel} - - - )} - - } + actions={{renderActions}} {...rest} /> - ) -); + ); +}); + +EmptyPageComponent.displayName = 'EmptyPageComponent'; +export const EmptyPage = React.memo(EmptyPageComponent); EmptyPage.displayName = 'EmptyPage'; diff --git a/x-pack/plugins/security_solution/public/common/translations.ts b/x-pack/plugins/security_solution/public/common/translations.ts index 413119fb40f14..3b94ac8959496 100644 --- a/x-pack/plugins/security_solution/public/common/translations.ts +++ b/x-pack/plugins/security_solution/public/common/translations.ts @@ -7,16 +7,39 @@ import { i18n } from '@kbn/i18n'; export const EMPTY_TITLE = i18n.translate('xpack.securitySolution.pages.common.emptyTitle', { - defaultMessage: 'Welcome to Security Solution. Let’s get you started.', + defaultMessage: 'Welcome to Elastic Security. Let’s get you started.', }); -export const EMPTY_ACTION_PRIMARY = i18n.translate( - 'xpack.securitySolution.pages.common.emptyActionPrimary', +export const EMPTY_ACTION_ELASTIC_AGENT = i18n.translate( + 'xpack.securitySolution.pages.common.emptyActionElasticAgent', + { + defaultMessage: 'Add data with Elastic Agent', + } +); + +export const EMPTY_ACTION_ELASTIC_AGENT_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.pages.common.emptyActionElasticAgentDescription', + { + defaultMessage: + 'The Elastic Agent provides a simple, unified way to add monitoring to your hosts.', + } +); + +export const EMPTY_ACTION_BEATS = i18n.translate( + 'xpack.securitySolution.pages.common.emptyActionBeats', { defaultMessage: 'Add data with Beats', } ); +export const EMPTY_ACTION_BEATS_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.pages.common.emptyActionBeatsDescription', + { + defaultMessage: + 'Lightweight Beats can send data from hundreds or thousands of machines and systems', + } +); + export const EMPTY_ACTION_SECONDARY = i18n.translate( 'xpack.securitySolution.pages.common.emptyActionSecondary', { @@ -27,6 +50,14 @@ export const EMPTY_ACTION_SECONDARY = i18n.translate( export const EMPTY_ACTION_ENDPOINT = i18n.translate( 'xpack.securitySolution.pages.common.emptyActionEndpoint', { - defaultMessage: 'Add data with Elastic Agent (Beta)', + defaultMessage: 'Add Elastic Endpoint Security', + } +); + +export const EMPTY_ACTION_ENDPOINT_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.pages.common.emptyActionEndpointDescription', + { + defaultMessage: + 'Protect your hosts with threat prevention, detection, and deep security data visibility.', } ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index 110620fad7eba..982712cbe9797 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -53,6 +53,9 @@ jest.mock('react-router-dom', () => { useHistory: jest.fn(), }; }); +jest.mock('../../components/alerts_info', () => ({ + useAlertInfo: jest.fn().mockReturnValue([]), +})); const state: State = { ...mockGlobalState, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_signal_index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_signal_index.tsx index 32ae585aec191..c315361b294c7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_signal_index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_signal_index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EmptyPage } from '../../../common/components/empty_page'; import * as i18n from './translations'; @@ -12,12 +12,21 @@ import { useKibana } from '../../../common/lib/kibana'; export const DetectionEngineNoIndex = React.memo(() => { const docLinks = useKibana().services.docLinks; + const actions = useMemo( + () => ({ + detections: { + icon: 'documents', + label: i18n.GO_TO_DOCUMENTATION, + url: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/security/${docLinks.DOC_LINK_VERSION}/detection-engine-overview.html#detections-permissions`, + target: '_blank', + }, + }), + [docLinks] + ); + return ( { const docLinks = useKibana().services.docLinks; - + const actions = useMemo( + () => ({ + detectionUnauthenticated: { + icon: 'documents', + label: i18n.GO_TO_DOCUMENTATION, + url: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/security/${docLinks.DOC_LINK_VERSION}/detection-engine-overview.html#detections-permissions`, + target: '_blank', + }, + }), + [docLinks] + ); return ( ({ + useIngestUrl: jest + .fn() + .mockReturnValue({ appId: 'ingestAppId', appPath: 'ingestPath', url: 'ingestUrl' }), +})); + +jest.mock('../../../common/hooks/endpoint/ingest_enabled', () => ({ + useIngestEnabledCheck: jest.fn().mockReturnValue({ allEnabled: true }), +})); + +jest.mock('../../../common/hooks/endpoint/use_navigate_to_app_event_handler', () => ({ + useNavigateToAppEventHandler: jest.fn(), +})); + +describe('OverviewEmpty', () => { + describe('When isIngestEnabled = true', () => { + let wrapper: ShallowWrapper; + beforeAll(() => { + wrapper = shallow(); + }); + + afterAll(() => { + (useIngestEnabledCheck as jest.Mock).mockReset(); + }); + + test('render with correct actions ', () => { + expect(wrapper.find('[data-test-subj="empty-page"]').prop('actions')).toEqual({ + beats: { + description: + 'Lightweight Beats can send data from hundreds or thousands of machines and systems', + fill: false, + label: 'Add data with Beats', + url: '/app/home#/tutorial_directory/security', + }, + elasticAgent: { + description: + 'The Elastic Agent provides a simple, unified way to add monitoring to your hosts.', + fill: false, + label: 'Add data with Elastic Agent', + url: 'ingestUrl', + }, + endpoint: { + description: + 'Protect your hosts with threat prevention, detection, and deep security data visibility.', + fill: false, + label: 'Add Elastic Endpoint Security', + onClick: undefined, + url: '/app/home#/tutorial_directory/security', + }, + }); + }); + }); + + describe('When isIngestEnabled = false', () => { + let wrapper: ShallowWrapper; + beforeAll(() => { + (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: false }); + wrapper = shallow(); + }); + + test('render with correct actions ', () => { + expect(wrapper.find('[data-test-subj="empty-page"]').prop('actions')).toEqual({ + beats: { + description: + 'Lightweight Beats can send data from hundreds or thousands of machines and systems', + fill: false, + label: 'Add data with Beats', + url: '/app/home#/tutorial_directory/security', + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx index 33413be10079e..1d2c6889213f1 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; +import { omit } from 'lodash/fp'; + import { FormattedMessage } from '@kbn/i18n/react'; import { EuiLink } from '@elastic/eui'; import * as i18nCommon from '../../../common/translations'; -import { EmptyPage } from '../../../common/components/empty_page'; +import { EmptyPage, EmptyPageActionsProps } from '../../../common/components/empty_page'; import { useKibana } from '../../../common/lib/kibana'; import { ADD_DATA_PATH } from '../../../../common/constants'; import { useIngestUrl } from '../../../management/pages/endpoint_hosts/view/hooks'; @@ -23,23 +25,45 @@ const OverviewEmptyComponent: React.FC = () => { ); const handleOnClick = useNavigateToAppEventHandler(ingestAppId, { path: ingestPath }); const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); + const emptyPageActions: EmptyPageActionsProps = useMemo( + () => ({ + elasticAgent: { + label: i18nCommon.EMPTY_ACTION_ELASTIC_AGENT, + url: ingestUrl, + description: i18nCommon.EMPTY_ACTION_ELASTIC_AGENT_DESCRIPTION, + fill: false, + }, + beats: { + label: i18nCommon.EMPTY_ACTION_BEATS, + url: `${basePath}${ADD_DATA_PATH}`, + description: i18nCommon.EMPTY_ACTION_BEATS_DESCRIPTION, + fill: false, + }, + endpoint: { + label: i18nCommon.EMPTY_ACTION_ENDPOINT, + url: `${basePath}${ADD_DATA_PATH}`, + description: i18nCommon.EMPTY_ACTION_ENDPOINT_DESCRIPTION, + onClick: handleOnClick, + fill: false, + }, + }), + [basePath, ingestUrl, handleOnClick] + ); + + const emptyPageIngestDisabledActions = useMemo( + () => omit(['elasticAgent', 'endpoint'], emptyPageActions), + [emptyPageActions] + ); return isIngestEnabled === true ? ( {i18nCommon.EMPTY_ACTION_SECONDARY} @@ -50,16 +74,13 @@ const OverviewEmptyComponent: React.FC = () => { /> ) : ( {i18nCommon.EMPTY_ACTION_SECONDARY} diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx index 286cc870378e1..74225c4e4f823 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx @@ -83,7 +83,7 @@ describe('Overview', () => { ); - expect(wrapper.find('[data-test-subj="empty-page-secondary-action"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="empty-page-endpoint-action"]').exists()).toBe(false); }); it('shows Endpoint get ready button when ingest is enabled', () => { @@ -95,7 +95,7 @@ describe('Overview', () => { ); - expect(wrapper.find('[data-test-subj="empty-page-secondary-action"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="empty-page-endpoint-action"]').exists()).toBe(true); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 423aa597a0129..1b743c259555a 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -17,6 +17,7 @@ import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { useWithSource } from '../../common/containers/source'; + import { EventsByDataset } from '../components/events_by_dataset'; import { EventCounts } from '../components/event_counts'; import { OverviewEmpty } from '../components/overview_empty'; @@ -66,7 +67,6 @@ const OverviewComponent: React.FC = ({ addMessage('management', 'dismissEndpointNotice'); }, [addMessage]); const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); - return ( <> {indicesExist ? ( diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b6aaa2065c795..8fbcb3f1122cc 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13572,7 +13572,7 @@ "xpack.securitySolution.detectionEngine.editRule.errorMsgDescription": "申し訳ありません", "xpack.securitySolution.detectionEngine.editRule.pageTitle": "ルール設定の編集", "xpack.securitySolution.detectionEngine.editRule.saveChangeTitle": "変更を保存", - "xpack.securitySolution.detectionEngine.emptyActionPrimary": "セットアップの手順を表示", + "xpack.securitySolution.detectionEngine.emptyActionBeats": "セットアップの手順を表示", "xpack.securitySolution.detectionEngine.emptyActionSecondary": "ドキュメントに移動", "xpack.securitySolution.detectionEngine.emptyTitle": "Securityアプリケーションの検出エンジンに関連したインデックスがないようです", "xpack.securitySolution.detectionEngine.goToDocumentationButton": "ドキュメンテーションを表示", @@ -14349,7 +14349,7 @@ "xpack.securitySolution.overview.viewEventsButtonLabel": "イベントを表示", "xpack.securitySolution.overview.winlogbeatMWSysmonOperational": "Microsoft-Windows-Sysmon/Operational", "xpack.securitySolution.overview.winlogbeatSecurityTitle": "セキュリティ", - "xpack.securitySolution.pages.common.emptyActionPrimary": "Beatsでデータを表示", + "xpack.securitySolution.pages.common.emptyActionBeats": "Beatsでデータを表示", "xpack.securitySolution.pages.common.emptyActionSecondary": "入門ガイドを表示", "xpack.securitySolution.pages.common.emptyTitle": "SIEMへようこそ。始めましょう。", "xpack.securitySolution.pages.fourohfour.noContentFoundDescription": "コンテンツがありません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index dcbcda120587f..b69763449a06f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13578,7 +13578,7 @@ "xpack.securitySolution.detectionEngine.editRule.errorMsgDescription": "抱歉", "xpack.securitySolution.detectionEngine.editRule.pageTitle": "编辑规则设置", "xpack.securitySolution.detectionEngine.editRule.saveChangeTitle": "保存更改", - "xpack.securitySolution.detectionEngine.emptyActionPrimary": "查看设置说明", + "xpack.securitySolution.detectionEngine.emptyActionBeats": "查看设置说明", "xpack.securitySolution.detectionEngine.emptyActionSecondary": "前往文档", "xpack.securitySolution.detectionEngine.emptyTitle": "似乎您没有与 Security 应用程序的检测引擎相关的索引", "xpack.securitySolution.detectionEngine.goToDocumentationButton": "查看文档", @@ -14355,7 +14355,7 @@ "xpack.securitySolution.overview.viewEventsButtonLabel": "查看事件", "xpack.securitySolution.overview.winlogbeatMWSysmonOperational": "Microsoft-Windows-Sysmon/Operational", "xpack.securitySolution.overview.winlogbeatSecurityTitle": "安全", - "xpack.securitySolution.pages.common.emptyActionPrimary": "使用 Beats 添加数据", + "xpack.securitySolution.pages.common.emptyActionBeats": "使用 Beats 添加数据", "xpack.securitySolution.pages.common.emptyActionSecondary": "查看入门指南", "xpack.securitySolution.pages.common.emptyTitle": "欢迎使用 SIEM。让我们教您如何入门。", "xpack.securitySolution.pages.fourohfour.noContentFoundDescription": "未找到任何内容", From f1c08939ead12341ccb2dabfe2bb9f8681de4e65 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Tue, 28 Jul 2020 20:21:11 -0400 Subject: [PATCH 74/75] [SECURITY_SOLUTION][Administration] Task/remove policy tab (#73352) --- .../components/management_empty_state.tsx | 63 ++----------------- .../components/management_page_view.tsx | 45 +------------ .../view/details/host_details.tsx | 6 +- .../endpoint_hosts/view/details/index.tsx | 4 +- .../pages/endpoint_hosts/view/index.test.tsx | 4 +- .../pages/endpoint_hosts/view/index.tsx | 4 +- .../public/management/pages/policy/index.tsx | 8 +-- .../configure_package_config.tsx | 58 +++++++---------- .../pages/policy/view/policy_details.test.tsx | 14 ++--- .../pages/policy/view/policy_details.tsx | 19 +++--- .../pages/policy/view/policy_list.test.tsx | 3 +- .../endpoint_overview/translations.ts | 4 +- .../apps/endpoint/endpoint_list.ts | 10 +-- .../apps/endpoint/index.ts | 1 - .../apps/endpoint/policy_details.ts | 4 +- 15 files changed, 68 insertions(+), 179 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx b/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx index fb9f97f3f7570..a4518d1a1f493 100644 --- a/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx +++ b/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx @@ -77,57 +77,6 @@ const PolicyEmptyState = React.memo<{ /> - - - - - - - - -

      - -

      -
      -
      -
      - - - - -
      - - - - - - - -

      - -

      -
      -
      -
      - - - - -
      -
      - [ { title: i18n.translate('xpack.securitySolution.endpoint.hostList.stepOneTitle', { - defaultMessage: 'Select the policy you want to use to protect your hosts', + defaultMessage: 'Select the integration you want to use', }), children: ( <> @@ -203,7 +152,7 @@ const HostsEmptyState = React.memo<{ ) : selectionOptions.length ? ( @@ -211,7 +160,7 @@ const HostsEmptyState = React.memo<{ ) : ( ); }} @@ -263,13 +212,13 @@ const HostsEmptyState = React.memo<{ headerComponent={ } bodyComponent={ } /> diff --git a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx index 42341b524362d..aa562b9a20201 100644 --- a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx +++ b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx @@ -4,52 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, useMemo } from 'react'; -import { i18n } from '@kbn/i18n'; -import { useParams } from 'react-router-dom'; +import React, { memo } from 'react'; import { PageView, PageViewProps } from '../../common/components/endpoint/page_view'; -import { AdministrationSubTab } from '../types'; -import { SecurityPageName } from '../../app/types'; -import { useFormatUrl } from '../../common/components/link_to'; -import { getHostListPath, getPoliciesPath } from '../common/routing'; -import { useNavigateByRouterEventHandler } from '../../common/hooks/endpoint/use_navigate_by_router_event_handler'; export const ManagementPageView = memo>((options) => { - const { formatUrl, search } = useFormatUrl(SecurityPageName.administration); - const { tabName } = useParams<{ tabName: AdministrationSubTab }>(); - - const goToEndpoint = useNavigateByRouterEventHandler( - getHostListPath({ name: 'hostList' }, search) - ); - - const goToPolicies = useNavigateByRouterEventHandler(getPoliciesPath(search)); - - const tabs = useMemo((): PageViewProps['tabs'] | undefined => { - if (options.viewType === 'details') { - return undefined; - } - return [ - { - name: i18n.translate('xpack.securitySolution.managementTabs.hosts', { - defaultMessage: 'Hosts', - }), - id: AdministrationSubTab.hosts, - isSelected: tabName === AdministrationSubTab.hosts, - href: formatUrl(getHostListPath({ name: 'hostList' })), - onClick: goToEndpoint, - }, - { - name: i18n.translate('xpack.securitySolution.managementTabs.policies', { - defaultMessage: 'Policies', - }), - id: AdministrationSubTab.policies, - isSelected: tabName === AdministrationSubTab.policies, - href: formatUrl(getPoliciesPath()), - onClick: goToPolicies, - }, - ]; - }, [formatUrl, goToEndpoint, goToPolicies, options.viewType, tabName]); - return ; + return ; }); ManagementPageView.displayName = 'ManagementPageView'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx index cea66acbef8ca..109392cb7a929 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx @@ -121,7 +121,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { return [ { title: i18n.translate('xpack.securitySolution.endpoint.host.details.policy', { - defaultMessage: 'Policy', + defaultMessage: 'Integration', }), description: ( <> @@ -136,7 +136,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { }, { title: i18n.translate('xpack.securitySolution.endpoint.host.details.policyStatus', { - defaultMessage: 'Policy Status', + defaultMessage: 'Configuration response', }), description: ( { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx index 71b3885308558..212c8977a8852 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx @@ -158,7 +158,7 @@ const PolicyResponseFlyoutPanel = memo<{

      @@ -167,7 +167,7 @@ const PolicyResponseFlyoutPanel = memo<{ title={ } /> diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 47227244b7066..9d49c8705affe 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -431,7 +431,7 @@ describe('when on the hosts page', () => { const renderResult = render(); const linkToReassign = await renderResult.findByTestId('hostDetailsLinkToIngest'); expect(linkToReassign).not.toBeNull(); - expect(linkToReassign.textContent).toEqual('Reassign Policy'); + expect(linkToReassign.textContent).toEqual('Reassign Configuration'); expect(linkToReassign.getAttribute('href')).toEqual( `/app/ingestManager#/fleet/agents/${agentId}/activity?openReassignFlyout=true` ); @@ -492,7 +492,7 @@ describe('when on the hosts page', () => { it('should include the sub-panel title', async () => { expect( (await renderResult.findByTestId('hostDetailsPolicyResponseFlyoutTitle')).textContent - ).toBe('Policy Response'); + ).toBe('Configuration Response'); }); it('should show a configuration section for each protection', async () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index e38ef1bd5fe86..2692f7791b7c0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -237,7 +237,7 @@ export const HostList = () => { { field: 'metadata.Endpoint.policy.applied', name: i18n.translate('xpack.securitySolution.endpointList.policy', { - defaultMessage: 'Policy', + defaultMessage: 'Integration', }), truncateText: true, // eslint-disable-next-line react/display-name @@ -256,7 +256,7 @@ export const HostList = () => { { field: 'metadata.Endpoint.policy.applied', name: i18n.translate('xpack.securitySolution.endpointList.policyStatus', { - defaultMessage: 'Policy Status', + defaultMessage: 'Configuration Status', }), // eslint-disable-next-line react/display-name render: (policy: HostInfo['metadata']['Endpoint']['policy']['applied'], item: HostInfo) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx index 5122bbcd5d55d..681f1f1430926 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx @@ -6,17 +6,13 @@ import React, { memo } from 'react'; import { Route, Switch } from 'react-router-dom'; -import { PolicyDetails, PolicyList } from './view'; -import { - MANAGEMENT_ROUTING_POLICIES_PATH, - MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, -} from '../../common/constants'; +import { PolicyDetails } from './view'; +import { MANAGEMENT_ROUTING_POLICY_DETAILS_PATH } from '../../common/constants'; import { NotFoundPage } from '../../../app/404'; export const PolicyContainer = memo(() => { return ( - diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_config.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_config.tsx index 67f24977406c6..2b08bfd2b282b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_config.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_config.tsx @@ -6,8 +6,7 @@ import React, { memo, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCallOut, EuiText, EuiTitle, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { EuiCallOut, EuiText, EuiSpacer } from '@elastic/eui'; import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app'; import { CustomConfigurePackageConfigContent, @@ -50,52 +49,37 @@ export const ConfigureEndpointPackageConfig = memo - -

      - -

      -

      {from === 'edit' ? ( - <> - - - - - - + + + + ), + }} + /> ) : ( )}

      diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx index 4f7c14735fe21..6ed4e06ee51c5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx @@ -10,7 +10,7 @@ import { mount } from 'enzyme'; import { PolicyDetails } from './policy_details'; import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; -import { getPolicyDetailPath, getPoliciesPath } from '../../../common/routing'; +import { getPolicyDetailPath, getHostListPath } from '../../../common/routing'; import { apiPathMockResponseProviders } from '../store/policy_list/test_mock_utils'; jest.mock('../../../../common/components/link_to'); @@ -19,7 +19,7 @@ describe('Policy Details', () => { type FindReactWrapperResponse = ReturnType['find']>; const policyDetailsPathUrl = getPolicyDetailPath('1'); - const policyListPathUrl = getPoliciesPath(); + const hostListPath = getHostListPath({ name: 'hostList' }); const sleep = (ms = 100) => new Promise((wakeup) => setTimeout(wakeup, ms)); const generator = new EndpointDocGenerator(); let history: AppContextTestRender['history']; @@ -127,8 +127,8 @@ describe('Policy Details', () => { const backToListButton = pageHeaderLeft.find('EuiButtonEmpty'); expect(backToListButton.prop('iconType')).toBe('arrowLeft'); - expect(backToListButton.prop('href')).toBe(policyListPathUrl); - expect(backToListButton.text()).toBe('Back to policy list'); + expect(backToListButton.prop('href')).toBe(hostListPath); + expect(backToListButton.text()).toBe('Back to endpoint hosts'); const pageTitle = pageHeaderLeft.find('[data-test-subj="pageViewHeaderLeftTitle"]'); expect(pageTitle).toHaveLength(1); @@ -141,7 +141,7 @@ describe('Policy Details', () => { ); expect(history.location.pathname).toEqual(policyDetailsPathUrl); backToListButton.simulate('click', { button: 0 }); - expect(history.location.pathname).toEqual(policyListPathUrl); + expect(history.location.pathname).toEqual(hostListPath); }); it('should display agent stats', async () => { await asyncActions; @@ -173,7 +173,7 @@ describe('Policy Details', () => { const navigateToAppMockedCalls = coreStart.application.navigateToApp.mock.calls; expect(navigateToAppMockedCalls[navigateToAppMockedCalls.length - 1]).toEqual([ 'securitySolution:administration', - { path: policyListPathUrl }, + { path: hostListPath }, ]); }); it('should display save button', async () => { @@ -232,7 +232,7 @@ describe('Policy Details', () => { ); expect(warningCallout).toHaveLength(1); expect(warningCallout.text()).toEqual( - 'This action will update 5 hostsSaving these changes will apply updates to all endpoints assigned to this policy' + 'This action will update 5 hostsSaving these changes will apply updates to all endpoints assigned to this agent configuration.' ); }); it('should close dialog if cancel button is clicked', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index 288bc484c23b5..cd63991dbac93 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -42,7 +42,7 @@ import { PageViewHeaderTitle } from '../../../../common/components/endpoint/page import { ManagementPageView } from '../../../components/management_page_view'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { SecurityPageName } from '../../../../app/types'; -import { getPoliciesPath } from '../../../common/routing'; +import { getHostListPath } from '../../../common/routing'; import { useFormatUrl } from '../../../../common/components/link_to'; import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; import { MANAGEMENT_APP_ID } from '../../../common/constants'; @@ -56,7 +56,7 @@ export const PolicyDetails = React.memo(() => { application: { navigateToApp }, }, } = useKibana(); - const { formatUrl, search } = useFormatUrl(SecurityPageName.administration); + const { formatUrl } = useFormatUrl(SecurityPageName.administration); const { state: locationRouteState } = useLocation(); // Store values @@ -70,6 +70,7 @@ export const PolicyDetails = React.memo(() => { const [showConfirm, setShowConfirm] = useState(false); const [routeState, setRouteState] = useState(); const policyName = policyItem?.name ?? ''; + const hostListRouterPath = getHostListPath({ name: 'hostList' }); // Handle showing update statuses useEffect(() => { @@ -87,7 +88,7 @@ export const PolicyDetails = React.memo(() => { @@ -109,11 +110,11 @@ export const PolicyDetails = React.memo(() => { } }, [navigateToApp, notifications.toasts, policyName, policyUpdateStatus, routeState]); - const handleBackToListOnClick = useNavigateByRouterEventHandler(getPoliciesPath()); + const handleBackToListOnClick = useNavigateByRouterEventHandler(hostListRouterPath); const navigateToAppArguments = useMemo((): Parameters => { - return routeState?.onCancelNavigateTo ?? [MANAGEMENT_APP_ID, { path: getPoliciesPath() }]; - }, [routeState?.onCancelNavigateTo]); + return routeState?.onCancelNavigateTo ?? [MANAGEMENT_APP_ID, { path: hostListRouterPath }]; + }, [hostListRouterPath, routeState?.onCancelNavigateTo]); const handleCancelOnClick = useNavigateToAppEventHandler(...navigateToAppArguments); const handleSaveOnClick = useCallback(() => { @@ -162,11 +163,11 @@ export const PolicyDetails = React.memo(() => { iconType="arrowLeft" contentProps={{ style: { paddingLeft: '0' } }} onClick={handleBackToListOnClick} - href={formatUrl(getPoliciesPath(search))} + href={formatUrl(hostListRouterPath)} > {policyItem.name} @@ -306,7 +307,7 @@ const ConfirmUpdate = React.memo<{ >
      diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx index 047aa6918736e..e35c97698f5cb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx @@ -14,7 +14,8 @@ import { AppAction } from '../../../../common/store/actions'; jest.mock('../../../../common/components/link_to'); -describe('when on the policies page', () => { +// Skipping these test now that the Policy List has been hidden +describe.skip('when on the policies page', () => { let render: () => ReturnType; let history: AppContextTestRender['history']; let store: AppContextTestRender['store']; diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts index 34e3347b5ff9a..a7f1be3debb83 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts @@ -9,14 +9,14 @@ import { i18n } from '@kbn/i18n'; export const ENDPOINT_POLICY = i18n.translate( 'xpack.securitySolution.host.details.endpoint.endpointPolicy', { - defaultMessage: 'Endpoint policy', + defaultMessage: 'Integration', } ); export const POLICY_STATUS = i18n.translate( 'xpack.securitySolution.host.details.endpoint.policyStatus', { - defaultMessage: 'Policy status', + defaultMessage: 'Configuration Status', } ); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index 07667a140d090..85d0e56231643 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -36,8 +36,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { [ 'Hostname', 'Host Status', - 'Policy', - 'Policy Status', + 'Integration', + 'Configuration Status', 'Operating System', 'IP Address', 'Version', @@ -119,7 +119,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); // The integration does not work properly yet. Skipping this test for now. - it.skip('navigates to ingest fleet when the Reassign Policy link is clicked', async () => { + it.skip('navigates to ingest fleet when the Reassign Configuration link is clicked', async () => { await (await testSubjects.find('hostnameCellLink')).click(); await (await testSubjects.find('hostDetailsLinkToIngest')).click(); await testSubjects.existOrFail('fleetAgentListTable'); @@ -145,8 +145,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'OS', 'Last Seen', 'Alerts', - 'Policy', - 'Policy Status', + 'Integration', + 'Configuration Status', 'IP Address', 'Hostname', 'Sensor Version', diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index eec3da4ce1c5e..7962ec60ff57e 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -29,7 +29,6 @@ export default function (providerContext: FtrProviderContext) { await ingestManager.setup(); }); loadTestFile(require.resolve('./endpoint_list')); - loadTestFile(require.resolve('./policy_list')); loadTestFile(require.resolve('./policy_details')); }); } diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 8b197a6c69a30..ca84384390a29 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -73,7 +73,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.existOrFail('policyDetailsSuccessMessage'); expect(await testSubjects.getVisibleText('policyDetailsSuccessMessage')).to.equal( - `Policy ${policyInfo.packageConfig.name} has been updated.` + `Integration ${policyInfo.packageConfig.name} has been updated.` ); }); it('should persist update on the screen', async () => { @@ -81,7 +81,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.policy.confirmAndSave(); await testSubjects.existOrFail('policyDetailsSuccessMessage'); - await pageObjects.policy.navigateToPolicyList(); + await pageObjects.endpoint.navigateToHostList(); await pageObjects.policy.navigateToPolicyDetails(policyInfo.packageConfig.id); expect(await (await testSubjects.find('policyWindowsEvent_process')).isSelected()).to.equal( From 78aa24dbd6e61c05559d7432d4357af1c2ad81b3 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 28 Jul 2020 19:30:42 -0500 Subject: [PATCH 75/75] Make button appear clickable, change state of empty text (#73496) Co-authored-by: Elastic Machine --- .../public/components/saved_views/toolbar_control.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index db9f92532645f..2e06ee55189d9 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -161,7 +161,11 @@ export function SavedViewsToolbarControls(props: Props) { /> - + (props: Props) { {currentView ? currentView.name : i18n.translate('xpack.infra.savedView.unknownView', { - defaultMessage: 'Unknown', + defaultMessage: 'No view seleted', })}